OutOfMemoryError JVM内存溢出
堆内存溢出
Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径 来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会 产生内存溢出异常。
Java堆内存的OutOfMemoryError
异常是实际应用中最常见的内存溢出异常情况。出现Java堆内存 溢出时,异常堆栈信息java.lang.OutOfMemoryError
会跟随进一步提示Java heap space
。
要解决这个内存区域的异常,常规的处理方法是首先通过内存映像分析工具对Dump出来的堆转储快照进行分析。第一步首先应确认内存中导致OOM的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak) 还是 内存溢出(Memory Overflow)。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎 样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们,根据泄漏对象的类型信息 以及它到GC Roots引用链的信息,一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。
如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机 的堆参数(-Xmx
与-Xms
)设置,与机器的内存对比,看看是否还有向上调整的空间。再从代码上检查 是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。
虚拟机栈和本地方法栈溢出
由于HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说,栈容量只能由-Xss
参数来设定。
虚拟机栈和本地方法栈在《Java虚拟机规范》中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将 抛 出
StackOverflowError
异 常 。 - 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出
OutOfMemoryError
异常。
《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机 的选择是不支持扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现 OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError
异常 。
方法区和运行时常量池溢出
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,要达成的条件是比较苛刻的。在经常运行时生成大量动态类的应用场景里,就应该特别关注这些类的回收状况。
在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。在默认设置下,已经很难再迫使虚拟机产生方法区的溢出异常了。不过HotSpot还是提供了一 些参数作为元空间的防御措施,主要包括:
-XX:MaxMetaspaceSize
:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。-XX:MetaspaceSize
:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize
(如果设置了的话)的情况下,适当提高该值。-XX:MinMetaspaceFreeRatio
:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可 减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio
,用于控制最 大的元空间剩余容量的百分比。
本机直接内存溢出
直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize
参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx
指定)一致。
由直接内存导致的内存溢出,一个明显的特征是在Heap Dump
文件中不会看见有什么明显的异常情况,如果发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectM emory
(常见的使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。