当你写的 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)这个工具,它就像内存界的 “法医”,能帮你找出内存占用大户和泄漏疑点:
- 加载文件:打开 MAT,导入之前生成的堆转储文件。
- 找大对象:在 “Histogram” 视图里,能看到哪些对象类型占用内存最多。比如发现
java.util.ArrayList实例占了超多内存,就得重点关注。 - 找泄漏点:用 “Leak Suspects Report” 功能,它会自动分析可能的内存泄漏地方,顺着线索就能找到问题根源。
第四步:在代码里找问题
找到内存占用大户后,就得检查代码了,看看是不是这些地方出了问题:
-
大对象集合:像
List、Map这类集合,如果不断往里塞数据,没有限制,很容易撑爆内存。 -
长生命周期对象:有些对象本来不需要一直存在,却一直占着内存不释放。
常见的 “坑” 有:
- 缓存没清理:自己写的缓存,如果没有过期或清理机制,缓存数据会越积越多。
- 数据结构无限增长:比如把所有请求数据都存到一个集合里,不做限制,最后内存肯定扛不住。
第五步:优化代码和调整配置
找到问题就该动手解决了:
-
优化数据结构:根据实际需求,选更合适的数据结构,比如用
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 错误。按照上面的步骤:
- 先配置 JVM 参数收集证据:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump -Xlog:gc*:file=gc.log:time。 - 程序崩溃后,拿到堆转储文件。
- 用 MAT 分析,发现
Session对象占用大量内存,进一步查看发现自定义的Session管理模块没及时释放过期的Session,导致内存被占满。 - 优化代码,给
Session加上超时机制,定期清理过期的Session:
public void cleanupSessions() {
sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
-
调整堆内存配置,从
-Xmx512m增加到-Xmx1g,然后重新做负载测试。 -
配置内存监控和告警。
经过这些操作,应用程序在高负载下也能稳定运行,再也没出现过 OOM 错误。只要按照这个流程排查,大部分 OOM 问题都能迎刃而解!