前言
之前我们简单的介绍了下虚拟机内存结构,今天我们讲下虚拟机的内存相关错误异常,主要从几大内存区域分类介绍:Java堆溢出、虚拟机栈和本地方法栈溢出、方法区和运行时常量池溢出、本机直接内存溢出。
Java堆溢出
-
什么时候产生堆溢出?
-
堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到了最大堆容量限制时就会产生堆溢出异常;
-
-
堆溢出如何分析?
-
通过设置参数:-XX:+HeapDumpOnOutOfMemoryError,当虚拟机产生堆溢出异常时就会生成内存堆转储快照;
-
通过分析堆转储快照分析堆溢出的原因是存在内存泄露还是内存溢出;
-
若是内存泄露,可通过进一步查看泄露对象到GC Roots的引用链;
-
若不是内存泄露,即内存溢出,那么就应当检查虚拟机的堆参数(-Xmx,-Xms)与物理内存对比是否还能继续调大,检查代码中是否存在某些对象生命周期过长、持有状态时间过长,尝试减小程序运行时内存消耗;
-
-
虚拟机栈和本地方法栈溢出
上节我们有提到某些虚拟机将虚拟机栈和本地方法栈合二为一,所以栈的容量本质上只由-Xss参数设置
-
什么时候产生栈溢出?
-
当线程请求的栈深度大于虚拟机所允许的最大深度时,抛出StackOverflowError异常;
-
当虚拟机在扩展栈时无法申请到足够的内存空间,就会抛出OutOfMemoryError;
-
单线程环境下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法分配时,都会产生StackOverflowError异常;
-
多线程环境下,通过不断创建线程会存在内存溢出情况,这种情况下,为每个线程的栈分配的内存越大,反而越容易产生内存溢出;
-
-
如何分析栈溢出?
-
操作系统分配给每个线程的内存是有限的,虚拟机提供了堆和方法区两个部分的最大内存设置,除掉这两部分内存,剩下的内存基本上就是栈的,每个线程分配到的栈内存越大,可以分配的线程总数就越小,建立线程时就越容易把剩下的内存耗尽,因此这部分溢出解决并没有太好的方案,只能通过加大物理机的内存;
-
当多线程环境下时,如果栈溢出是由于建立了太多的线程导致,那么可以通过减少最大堆和减少栈容量来换取更多的线程数量;
-
方法区和运行时常量池溢出
之前我们提到过运行时常量池是方法区的一部分,所以这两块的溢出直接放一起,在JDK1.6及之前,可以通过设置-XX:PermSize和-XX:MaxPermSize来设置方法区大小的初始值和最大值;
-
什么时候产生方法区溢出?
-
我们知道方法区是存放Class的相关信息,因此当运行时产生大量的类,填满方法区时就会产生溢出异常;
-
常见的CGLib字节码增强和动态语言,大量的JSP或动态产生JSP文件,基于OSGI的应用等容易产生方法区溢出;
-
-
如何分析方法区溢出?
-
方法区溢出是很常见的内存溢出异常,在经常产生大量Class的应用中,我们要特别注意类的垃圾回收状况,即使减少大量类无法回收的问题;
-
本机直接内存溢出
DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认和Java堆最大值一样(-Xmx);
由本机直接内存溢出一个明显的特征是在内存堆转储快照文件中不会看见明显的异常,当发生内存溢出异常之后堆转储文件又很小的情况下,就可以检查程序中是否直接或者间接使用了NIO。
相关文章:JVM系列之Java内存区域 https://blog.csdn.net/chengyabingfeiqi/article/details/106559475