"我的代码OOM了,怎么办?"
"报什么错?"
"OOM啊,java.lang.OutOfMemoryError"
"……"
OOM大约是Java程序员绕不过去的梗,但OOM应该怎么排查呢?
什么是OOM
OOM大家都知道,是内存溢出,通俗的说,就是程序运行需要的内存,虚拟机给不了,所以程序就撂挑子不干了。
OOM产生原因
1- 内存分配不足——JVM启动参数指定的内存大小不够 2- 理论上分配够了,但是程序使用内存超预期——内存泄漏|溢出
内存泄漏:程序已经使用过的内存没有得到回收,此时这部分内存不能分配给其他人但又不被使用。 内存溢出:程序需要的内存超过了虚拟机能提供的内存大小
OOM类型
OOM类型有很多种,针对不同类型可使用不同的排查手段
java.lang.OutOfMemoryError:Java heap space
最常见的OOM类型之一了,堆空间不足,举个栗子,程序运行需要20M,但是实际上JVM只提供了10M
举个栗子:
以下代码执行时设置JVM参数:-Xms10m -Xmx10m
-Xms 初始堆内存 10M
-Xmx 最大堆内存10M
public static void main(String[] args) {
String str = "黄河之水天上来";
while (true){
//循环创建字符串对象
str += str + new Random().nextInt(999999999);
//从常量池中获取字符串,若不存在,则创建一个字符串放到常量池中
str.intern();
}
}
运行结果:
java.lang.OutOfMemoryError:GC overhead limit exceeded
这个异常是指GC回收时间过长,时间过多耗费在GC中,但是回收效果不佳。
回收过长指的是连续多次超过98%的时间用来做GC,但是只回收了不到2%的堆内存,这样即使在GC清理后的内存也会很快再次填满,迫使GC再次执行,就此形成恶性循环,所以需要抛出异常
再举个栗子:
设置JVM参数:-Xms10m -Xmx20m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
PrintGCDetails 打印详细GC信息
MaxDirectMemorySize 当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC
public static void gcOverHead() {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true){
list.add(String.valueOf(++i).intern());
}
}catch (Throwable e){
e.printStackTrace();
throw e;
}
}
运行结果
java.lang.OutOfMemoryError: Direct buffer memory
直接内存溢出
原因分析:
直接内存崩溃,此处元空间并不在虚拟机中,而是使用本地内存,与GC无关。
常见于NIO程序中,使用ByteBuffer来读取和写入数据,这是基于通道channel和缓冲区buffer的IO方式,可以使用Native函数直接分配堆外内存,通过存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。该方式在某些常见中能提高性能,因为避免了java堆和native堆中来回拷贝数据
还是举个栗子:
设置JVM参数:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
public static void directBufferMemory() {
System.out.println("本地内存 = " + (VM.maxDirectMemory() / 1024 / 1024) + "MB");
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
//allocateDirect 分配直接内存
ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
运行结果: