OOM即内存溢出,是指程序在申请内存时,没有足够的内存空间供其使用,导致程序无法正常运行。排查OOM的问题可以通过监控内存使用情况、分析Heap Dump文件、使用命令行工具等方法。
一 确认问题
日志检查:查看应用的日志,确认是否有 OOM 错误的记录。通常,OOM 错误会在日志中显示为 java.lang.OutOfMemoryError
。
监控工具:使用监控工具(如 Prometheus, Grafana, New Relic, Datadog)检查内存使用情况,确定是否达到或超过了预期的内存限制。
二 确定 OOM 类型
OOM 错误可能有不同的类型,每种类型的处理方法可能不同:
Java Heap Space:通常表示堆内存不足,可能是由于内存泄漏或过多的数据存储在堆中。
Metaspace:表示类元数据区不足,通常与类加载和卸载有关。
GC Overhead Limit Exceeded:表示垃圾回收耗费了过多时间,可能是因为频繁的垃圾回收或大量的短生命周期对象。
Direct Buffer Memory:表示直接内存不足,通常与 ByteBuffer
或其他直接内存操作有关。
三 分析堆转储
生成堆转储:通过 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError
启用在 OOM 错误时生成堆转储。
分析堆转储:使用工具如 Eclipse MAT (Memory Analyzer Tool)、VisualVM、JProfiler 等分析堆转储,查找内存泄漏和高占用内存的对象。
MAT使用方法:
1.加载Heap Dump:使用MAT打开生成的堆转储文件。
2.查找大对象:使用MAT的“Histogram”视图查看占用内存最大的对象类型。3.查找泄漏疑点:使用“Leak Suspects Report”功能自动分析可能的内存泄漏点。
四 排查内存泄漏
代码审查:检查代码中可能导致内存泄漏的地方,例如未关闭的资源(文件、网络连接)、静态集合中的过期对象等。
使用分析工具:使用内存分析工具(如 Eclipse MAT、JProfiler)识别长时间存活的大对象或不断增加的对象。
五 调整 JVM 配置
调整堆内存大小:使用 JVM 参数 -Xms
和 -Xmx
调整初始和最大堆内存大小。例如,-Xmx4g
设置最大堆内存为 4GB。
调整垃圾回收器:根据应用的需求选择适当的垃圾回收器(如 G1、CMS、Parallel GC)和调整相关参数(如 -XX:NewSize
、-XX:MaxGCPauseMillis
)。
调整元空间大小:如果遇到 Metaspace 问题,可以使用 -XX:MaxMetaspaceSize
来调整 Metaspace 的大小。
六 优化代码和数据结构
优化算法:检查是否有低效的算法导致内存大量使用,尝试优化算法。
优化数据结构:选择适当的数据结构,以减少内存使用。例如,避免使用过大的集合或不必要的缓存。
七 增加内存资源
增加物理内存:如果应用的内存需求合理但超出了当前服务器的内存限制,可以考虑增加服务器的物理内存。
水平扩展:考虑将应用分布到多个实例中,通过水平扩展来分担内存负载。
八 代码审查和重构
审查使用:审查代码中的内存使用模式,找出潜在的内存泄漏或过高的内存使用。
重构:根据分析结果进行代码重构,减少不必要的对象创建和持久化。
例如,在如下代码中:
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakDemo {
private static List<byte[]> memoryLeakList = new ArrayList<>();
public static void main(String[] args) {
while (true) {
byte[] largeArray = new byte[10 * 1024 * 1024]; // 10 MB
memoryLeakList.add(largeArray);
System.out.println("Added a 10MB object. Current size: " + memoryLeakList.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
这段代码持续向 memoryLeakList
中添加 10 MB 的字节数组对象,快速消耗内存。
当应用程序崩溃时,我们可以设置 JVM 参数以生成堆转储:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof -cp . MemoryLeakDemo
打开 MAT,导入生成的 heapdump.hprof
文件,使用 MAT 的“泄漏报告”功能来识别内存泄漏源。我们不难发现,是由于memoryLeakList
持续增长导致内存消耗不断增加。因此,我们可以限制 memoryLeakList
的大小,并在超出限制时移除最旧的对象,以避免内存泄漏。
import java.util.LinkedList;
import java.util.List;
public class MemoryLeakFixedDemo {
private static List<byte[]> memoryLeakList = new LinkedList<>();
public static void main(String[] args) {
while (true) {
byte[] largeArray = new byte[10 * 1024 * 1024]; // 10 MB
memoryLeakList.add(largeArray);
if (memoryLeakList.size() > 100) {
memoryLeakList.remove(0);
}
System.out.println("Added a 10MB object. Current size: " + memoryLeakList.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}