系统崩溃(OOM)

前言

程序outOfMemoryError

我的心里活动:"哈哈哈😀哈哈哈😀终于给我碰上了,这个问题可很少发生啊,又积累一个问题。虽然我昨天发了版本,但是作为公司技术最好,长得最帅的我,代码肯定不出现这个问题。"

不想看废话的直接看【解决过程和方案】 吧

排查过程

首先肯定是先看日志,根据日志信息可以看到三个关键信息:

  1. 业务代码中调用了远程接口
  2. 内存溢出outOfMemoryError
  3. Arrays.copyOf(Arrays.java:3332)

猜想❔

得到关键信息后,看了代码之后我就和技术经理做了一个简单的讨论

  • 技术经理:调用远程接口没有设置超时时间,访问人数突然增多,导致请求阻塞。瞬间一万个请求进来,然后一个万的请求就阻塞了,从而导致内存溢出

  • 我的猜想 :请求之前没有查询大量的数据,就算请求阻塞也不会占用大量内存,导致outOfMemory. 还有就是Tomcat的线程池也默认200,。就算一万个请求进来,同一时刻也最多处理200个请求,这个200个请求都阻塞的话会造成 系统卡死,但是不会出现outOfMemoryError

    虽然抛出了一个业务代码的错误,但是并不能肯定就是它引发的outOfMemoryError,也可能是其他任务占了太多内存,这个请求只是一根"稻草"

解决过程和方案✔

  1. 修改启动命令,导出堆信息

    定位outOfMemoryError 还得用证据说话呀! 让技术经理在启动命令上加了如下参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof 再次出现内存溢出就会将堆信息导出。

  2. 分析堆信息,定位大内存对象

    因为之前已经装过分析dump文件的工具了,我用的是Intellij Profiler工具,打开堆快照文件如下:

根本问题已经浮出水面了,KbResultSetList<UnionCustomerCheckLog>这两个对象几乎把内存撑满了,jvm 堆内存最大1024M 这两个就占了700M. 算是定位到问题了吧! 也推翻了技术经理说的调用远程接口大量请求被阻塞的问题。

  1. 查找GC回收日志
    我们搜索自己的业务包名字就行了,这样就能很快定位关键问题。发现存在service 名称 和 上面大对象的名称一致UnionCustomerCheckLog !

同样的搜索方式在summary栏目搜索,也能匹配上,并且定位到代码方法queryXXPassList()

  1. 定位问题代码 、分析代码
    找到关键代码之后我们就看看代码吧:
ini 复制代码
` select *  `? 这个写法是开发禁忌哦,加上这个`sql` 的查询的结果有40W 条了。

**看看这个查询有啥用呢❓ 看看伪代码吧**:

> 下面代码就是用代码实现的分组统计........没错就是我们 `Android` 工程师写的,因为公司去年就不用维护客户端了,安卓写java,ios写前端。  
> `刚好上午给老哥指出问题,下午就被人事约谈,领了大礼包了。`

```
java
 体验AI代码助手
 代码解读
复制代码
// 查询出需要的所属数据:40w条
List<UnionCustomerCheckLog> xxPassList = unionCustomerCheckLogService.queryXXPassList();

// 分组统计
Map<Integer, List<UnionCustomerCheckLog>> unionMap =
        xxPassList.stream().collect(Collectors.groupingBy(UnionCustomerCheckLog::getSource));
        
for (DictModel dict : modelList) {
    ChartVo vo = new ChartVo();
    vo.setName(dict.getText());
    List<UnionCustomerCheckLog> checkLogList = unionMap.get(Integer.valueOf(dict.getValue()));
    if (Objects.isNull(checkLogList)) {
        vo.setNumber(0L);
    } else {
        // 统计长度
        vo.setNumber((long) checkLogList.size());
    }
    voList.add(pieChartVo);
}
```
  1. 解决问题
    既然上面已经分析出问题所在了,就是Android老哥还不太会分组统计,就把数据全部查询到内存中统计。导致outOfMemoryError.
    解决方法修改统计,改成SQL分组统计就行了:
复制代码
程序逻辑也相应的调整就行了。

知识拓展

OutOfMemoryError 简单介绍

JVM 在内存不足时会先尝试 Full GC 回收无用对象。 如果 GC 后仍无法分配内存,才会抛出 OutOfMemoryError。 常见类型有:

  • 堆内存不足:java.lang.OutOfMemoryError: Java heap space

  • 元空间(方法区)不足:java.lang.OutOfMemoryError: Metaspace

  • 栈内存溢出:java.lang.StackOverflowError(本质是栈内存的 OOM)。

  • 直接内存不足:java.lang.OutOfMemoryError: Direct buffer memory

  • 垃圾回收(GC)超时:java.lang.OutOfMemoryError: GC overhead limit exceeded

    GC 时间占比过高 :JVM 在连续多次 GC 中,超过 98% 的时间用于垃圾回收
    回收效果极差 :每次 GC 后,堆内存释放量不足 2%
    持续触发:上述情况持续发生,导致 JVM 判定应用已无法通过 GC 恢复可用内存

OutOfMemoryError 系统会直接挂掉么?

不一定,主要看以下几个因素

  1. 取决于 OOM 的类型和影响范围
  • 堆内存 OOM

    如果未捕获异常,仅当前线程终止,其他线程可能正常处理请求。 但堆内存不足可能导致后续请求继续触发 OOM,最终系统逐渐不可用。

  • 元空间 OOM(如类加载失败):

    可能导致 JVM 无法加载新类,影响功能完整性。

  • 栈溢出(StackOverflowError)

    通常仅终止当前线程,但如果是主线程崩溃,整个进程会退出。

  1. JVM 配置的影响
  • -XX:+CrashOnOutOfMemoryError:强制 JVM 在 OOM 时立即崩溃,生成核心转储文件,系统不可用。
  • -XX:+HeapDumpOnOutOfMemoryError:仅生成堆转储文件,JVM 进程可能继续运行(但状态可能不稳定)。
  1. 代码设计
  • 如果捕获了 OutOfMemoryError 并尝试释放资源(如关闭连接、清理缓存),可能恢复部分功能。

  • 但 OOM 通常是系统性问题的表现,即使捕获异常,应用状态可能已不可靠。

相关推荐
码农刚子3 小时前
ASP.NET Core Blazor简介和快速入门 二(组件基础)
javascript·后端
间彧3 小时前
Java ConcurrentHashMap如何合理指定初始容量
后端
catchadmin3 小时前
PHP8.5 的新 URI 扩展
开发语言·后端·php
少妇的美梦3 小时前
Maven Profile 教程
后端·maven
白衣鸽子3 小时前
RPO 与 RTO:分布式系统容灾的双子星
后端·架构
Jagger_3 小时前
SOLID原则与设计模式关系详解
后端
间彧4 小时前
Java: HashMap底层源码实现详解
后端
这里有鱼汤4 小时前
量化的困局:当所有人都在跑同一个因子时,我们还能赚谁的钱?
后端·python
武子康4 小时前
大数据-130 - Flink CEP 详解 - 捕获超时事件提取全解析:从原理到完整实战代码教程 恶意登录案例实现
大数据·后端·flink