OutOfMemoryError JVM内存溢出

OutOfMemoryError JVM内存溢出

马草原 508 2021-04-25

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),那就可以考虑重点检查一下直接内存方面的原因了。