OOM异常分析-JDK1.8
以下知识均基于jdk1.8
在javavirtualmachinespecification说明中,除程序计数器外,虚拟机内存的其他几个运行时区域可能会出现内存外错(oom )异常。
这次共享的目的有两个:
第一,通过代码验证Java虚拟机规范中描述的每个运行时区域存储的内容。
二是日常工作中遇到实际内存溢出异常时,根据异常信息快速判断是哪个区域的内存溢出,用什么代码来判断这些区域的内存溢出发生,以及这些异常发生后如何处理
Java堆溢出(Java.lang.out of memory error 3360 javaheapspace () ) ) ) ) ) ) )。
Java堆用于存储对象实例。 如果不断创建对象,并确保GC Roots到对象的可用路径,从而避免垃圾回收机制清除这些对象,则在对象数量达到最大团队容量限制后会发生内存溢出异常。
通过在-Xms参数中设置堆最小值,并将堆最大值-Xmx设置为相同,避免堆自动扩展
代码清单:
public Result heapOOM
列表列表=new ArrayList (;
while (真)。
list.add(newoomobject ) );
}
}
启动参数:-XM s60 m-xmx 60 m-xx : metaspacesize=128 m-xx : maxmetaspacesize=128 m-xx 3360 use g1gc-xx 3360 maxgcpausemise
执行结果:
exceptioninthread ' http-nio-80-exec-9 ' Java.lang.out of memory error 3360 javaheapspace
2019-12-1217336037336051,576 [ nioblockingselector.block poller-1 ] error [ org.Apache.Tomcat.util.net.ning
Java.lang.out of memory error : javaheapspace
2019-12-12 17:37336051,626 [ http-nio-80-exec-8 ] error [ o.a.c.c.c.[.[ localhost ].[/] ].[ incontextwithpath [ ] threw exception [ handlerdispatchfailed; nestedexceptionisjava.lang.out of memory error : javaheapspace ] withrootcause
Java.lang.out of memory error : javaheapspace
解决方案,常见的方法是首先使用内存映像分析工具(MAT )分析从Dump发出的堆转储快照。 重要的是确认是否需要内存中的对象。 这意味着明确是发生了“内存泄漏”,还是发生了内存溢出(Memory Overflow )
如果是内存泄漏,还可以使用工具查看从泄漏对象到GC Roots的引用链。 由此,可以找出泄漏对象通过什么样的路径与CG Roots相关,以及垃圾收集器不能自动回收。 通过掌握泄漏对象的类型信息和GC Roots参考链信息,可以相对准确地识别泄漏代码的位置。
如果不存在泄漏,则必须检查虚拟机的堆参数-Xms -Xmx (),以确定其是否可以稍大于计算机的物理内存。 此外,可以从代码中检查特定对象的声明周期是否过长或保留状态时间是否过长,以尝试在程序运行时减少内存消耗。
虚拟机堆栈和本地方法堆栈溢出
由于HotSpot虚拟机不区分虚拟机堆栈和本地方法堆栈,因此对于HotSpot,存在-Xoss参数(这是本地方法堆栈大小),但实际上无效,堆栈容量为-Xss 关于虚拟机堆栈和本地方法堆栈,Java虚拟机规范描述了以下两个异常:
如果线程请求的堆栈深度大于虚拟机允许的最大深度,则会抛出堆栈溢出错误异常。
如果虚拟机在堆栈扩展期间无法请求足够的内存空间,则会抛出内存错误输出异常。
测试代码:
公共类javavmstacksof {
私密堆叠长度=1;
公共语音堆栈leak
>stackLength++;stackLeak();
}
}
运行结果:
2019-12-13 11:01:00,511 [http-nio-80-exec-9] ERROR [o.a.c.c.C.[.[localhost].[/].[dispatcherServlet]] DirectJDKLog.java:182 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at com.gaodun.ts.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
在单线程中,由于栈帧太大(虚拟机栈容量太小),当内存无法分配的时候,抛出 StackOverflowError 异常。
也可以使用-Xss参数减少栈内存,也是可以抛出 StackOverflowError 异常。
元空间溢出
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制, 但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
测试代码:
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
启动参数: -Xms1024m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Denv=dev -Deureka.client.register-with-eureka=false -Deureka.client.enabled=false
运行结果:
Exception in thread "SimplePauseDetectorThread_0" Exception in thread "http-nio-80-exec-1" java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: Metaspace
Java7,将常量池是存放到了堆中,常量池就相当于是在永久代中,所以永久代存放在堆中。
Java8,取消了整个永久代区域,取而代之的是元空间。没有再对常量池进行调整。
本机直接内存溢出
DirectMemory 容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样
直接通过反射获取Unsafe实例进行内存分配(Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例,
也就是设计者希望只有rt.jar中的类才能使用Unsafe的功能)。
测试代码:
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
// 1M
unsafe.allocateMemory(1024 * 1024);
}
启动参数:-Xms100m -Xmx100m -XX:MaxDirectMemorySize=100m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Denv=dev -Deureka.client.register-with-eureka=false -Deureka.client.enabled=false
运行结果:
2019-12-13 13:12:32,547 [http-nio-80-exec-1] ERROR [o.a.c.c.C.[.[localhost].[/].[dispatcherServlet]] DirectJDKLog.java:182 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError] with root cause
java.lang.OutOfMemoryError: null
at sun.misc.Unsafe.allocateMemory(Native Method)
由于直接内存溢出,一个明显的特征是在Heap Dump 文件中不会看到明显的异常,如果你发现OOM之后Dump文件很小, 而程序中有直接或者间接使用了NIO,可以考虑一下是不是这方面的原因。
问题延伸
JVM 运行时数据区域&JVM内存模型
介绍下 Java 内存区域(运行时数据区)
Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)
对象的访问定位的两种方式(句柄和直接指针两种方式)
方法区和永久代的关系
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
String 类和常量池
8种基本类型的包装类和常量池
OOM分析
JVM垃圾收集器有哪些,分别对应的垃圾收集算法是怎样的
JVM调优经验
参考文档
《深入理解Java虚拟机》