OOM 异常 (OutOfMemoryError)
Java 堆溢出
出现标志:java.lang.OutOfMemoryError: Java heap space
解决方法:
- 先通过内存对象分析工具分析Dump出来的堆转存储快照,确认内存中的对象是否是必要的,级分清楚是出现了内存泄漏还是内存溢出;
- 如果是内存泄漏,通过攻击查看泄漏对象到GC Root的引用链,定位出泄漏的位置;
- 如果不存在泄漏,检查虚拟机对参数(-Xmx和-Xms)是否可以调大,检查代码中是否有哪些对象的生命周期过长,尝试减少程序运行期的内存消耗。
虚拟机参数:-XX:HeapDumpOnOutOfMemoryError:让虚拟机在出现内存泄漏异常时 Dump 出当前的内存堆转储快照用于事后分析。
例如:
Java堆溢出,Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Root到对象之间有可达路径来避免垃圾回收,那么对象数量到达最大堆的容量限制后就会产生内存溢出异常。
package com.mall.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
}
运行结果
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid18004.hprof ... Heap dump file created [28624826 bytes in 0.092 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.mall.jvm.HeapOOM.main(HeapOOM.java:18)
Java 虚拟机栈和本地方法栈溢出
- 单线程下,栈帧过大,虚拟机容量过小都不会导致OutOfMemoryError,只会导致StackOverflowError(栈会比内存先爆掉),一般多线程才会出现OutOfMemoryError,因为线程本身要占用内存
- 如果是多线程导致的OutOfMemoryError,在不能减少线程数或更换64位虚拟机的情况,只能通过减少最大堆和减少栈容量来换取更多的线程;(这个调节思路和 Java 堆出现 OOM 正好相反,Java 堆出现 OOM 要调大堆内存的设置值,而栈出现 OOM 反而要调小)
例如:栈(StackOverflowError)
如果线程请求的栈深度大于虚拟机栈所允许的最大深度,将会抛出StackOverflowError异常
package com.mall.jvm;
/**
* -Xss128k
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
try {
javaVMStackSOF.stackLeak();
}catch (Exception e){
System.out.println("stack length :" + javaVMStackSOF.stackLength);
throw e;
}
}
}
运行结果:
Exception in thread "main" java.lang.StackOverflowError at com.mall.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
栈(OutOfMemoryError)
如果虚拟机在拓展栈时无法申请到足够的空间则抛出OutOfMemoryError异常
package com.mall.jvm;
/**
* -Xss2M
*/
public class JavaVMStackOOM {
private void dontStop(){
while(true){
}
}
public void stackLeakByThread(){
while (true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
javaVMStackOOM.stackLeakByThread();
}
}
方法区和运行时常量池溢出
- 测试思路:产生大量的类去填满方法区,直到溢出;
- 在经常动态生成大量 Class 的应用中,如 Spring 框架(使用 CGLib 字节码技术),方法区溢出是一种常见的内存溢出,要特别注意类的回收状况。
直接内存溢出
- 出现特征:Heap Dump 文件中看不见明显异常,程序中直接或间接用了 NIO;
- 虚拟机参数:
-XX:MaxDirectMemorySize,如果不指定,则和-Xmx一样。
package com.ecut.exception;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* -Xmx20M -XX:MaxDirectMemorySize = 10M
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024*1024;
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true){
unsafe.allocateMemory(_1MB);
}
}
}
运行结果如下:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.ecut.exception.DirectMemoryOOM.main(DirectMemoryOOM.java:18)