OOM问题如何排查

542 阅读4分钟

当你写的 Java 程序突然抛出 OOM(OutOfMemoryError)错误,就好比汽车油箱见底还找不到加油站,程序直接 “趴窝” 了。这通常是因为程序把能申请到的内存全占满了,原因可能是内存泄漏,或者一开始给程序分配的内存就不够用。别慌,下面一步步教你揪出问题、解决问题。

第一步:搞清楚是哪种 OOM 错误

OOM 错误分好几种,先搞清楚类型才能对症下药:

  • java.lang.OutOfMemoryError: Java heap space:这是堆内存不够用了。堆就像程序的 “物资仓库”,存放各种对象实例,仓库满了,新对象就没地儿放了。
  • java.lang.OutOfMemoryError: Metaspace:JDK 8 及以上版本会遇到这种情况,元空间主要存类信息、方法数据这些,它满了也会报错。
  • java.lang.OutOfMemoryError: GC overhead limit exceeded:垃圾回收(GC)干活时间太长,一直清理不出空间,说明内存实在太紧张了。

第二步:收集破案线索

要找到问题,得先收集证据:

  • 错误日志:找到程序崩溃时打印的日志,里面藏着关键信息。
  • 堆转储文件(Heap dump) :可以设置让程序在 OOM 时自动生成这个文件,它记录了 OOM 那一刻堆内存里所有对象的状态。设置方法是在启动程序时加参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump,这样文件就会存到指定路径。
  • GC 日志:开启 GC 日志,看看垃圾回收过程有没有异常。加参数 -Xlog:gc*:file=gc.log:time 就能把 GC 信息输出到 gc.log 文件里。

第三步:用工具分析堆转储文件

推荐用 Eclipse Memory Analyzer(MAT)这个工具,它就像内存界的 “法医”,能帮你找出内存占用大户和泄漏疑点:

  1. 加载文件:打开 MAT,导入之前生成的堆转储文件。
  2. 找大对象:在 “Histogram” 视图里,能看到哪些对象类型占用内存最多。比如发现 java.util.ArrayList 实例占了超多内存,就得重点关注。
  3. 找泄漏点:用 “Leak Suspects Report” 功能,它会自动分析可能的内存泄漏地方,顺着线索就能找到问题根源。

第四步:在代码里找问题

找到内存占用大户后,就得检查代码了,看看是不是这些地方出了问题:

  • 大对象集合:像 ListMap 这类集合,如果不断往里塞数据,没有限制,很容易撑爆内存。

  • 长生命周期对象:有些对象本来不需要一直存在,却一直占着内存不释放。

常见的 “坑” 有:

  • 缓存没清理:自己写的缓存,如果没有过期或清理机制,缓存数据会越积越多。
  • 数据结构无限增长:比如把所有请求数据都存到一个集合里,不做限制,最后内存肯定扛不住。

第五步:优化代码和调整配置

找到问题就该动手解决了:

  • 优化数据结构:根据实际需求,选更合适的数据结构,比如用 LinkedList 代替 ArrayList 处理频繁插入删除的场景。

  • 及时清理对象:确定不再使用的对象,要让垃圾回收器能尽快回收它们。

  • 调整 JVM 参数:如果程序确实需要更多内存,可以加大堆内存。比如从 -Xmx512m 改成 -Xmx1g ,但也别盲目加,够用就行。

举个例子,原来代码里有个 List 无限增长:

List<String> largeList = new ArrayList<>();  
public void processData(List<String> data) {  
    largeList.addAll(data); // 增长无限制  
}

修改后加上限制:

private static final int MAX_SIZE = 10000;  
public void processData(List<String> data) {  
    if (largeList.size() + data.size() > MAX_SIZE) {  
        largeList.clear(); // 清理或采取措施避免无限增长  
    }  
    largeList.addAll(data);  
}

第六步:验证和监控

改完代码还不算完,还得确认问题真的解决了:

  • 做负载测试:模拟高并发场景,看看程序性能和内存使用是否正常。
  • 持续监控:用监控工具实时盯着内存使用情况,设置告警,一旦内存使用率过高就报警,防止问题再次出现。

实战案例

有个 Java Web 应用在高负载测试时,报了 java.lang.OutOfMemoryError: Java heap space 错误。按照上面的步骤:

  1. 先配置 JVM 参数收集证据:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump -Xlog:gc*:file=gc.log:time
  2. 程序崩溃后,拿到堆转储文件。
  3. 用 MAT 分析,发现 Session 对象占用大量内存,进一步查看发现自定义的 Session 管理模块没及时释放过期的 Session,导致内存被占满。
  4. 优化代码,给 Session 加上超时机制,定期清理过期的 Session
public void cleanupSessions() {  
    sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());  
}
  1. 调整堆内存配置,从 -Xmx512m 增加到 -Xmx1g,然后重新做负载测试。

  2. 配置内存监控和告警。

经过这些操作,应用程序在高负载下也能稳定运行,再也没出现过 OOM 错误。只要按照这个流程排查,大部分 OOM 问题都能迎刃而解!