JAVA:内存溢出异常

223 阅读5分钟
  • 本文已参与「新人创作礼」活动,一起开启掘金创作之路。

  • 在HotSpot虚拟机中,运行时数据区分为程序计数器,虚拟机栈,本地方法栈,java堆,方法区,除了程序计数器不会存在内存溢出的情况,别的区域都有可能发生内存溢出的异常。

  • Java堆溢出:

  • Java堆用于储存对象实例,如果新对象不断被创建,并且保证GC Roots到对象之间有可达路径 来避免垃圾回收机制清除这些对象,那么随着对象数量的不断增加,当总容量达到堆的最大容量限制后就会 产生OOM。

  • Java堆内存的OutOfMemoryError异常是实际应用中最常见的内存溢出异常情况。要解决堆中内存溢出的问题,首先应确认内存中导致OOM是出现了内存泄漏还是内存溢出。

    内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

    内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用。

  • 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是相关的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们,根据泄漏对象的类型信息以及它到GC Roots引用链的信息,一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。

  • 如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机的堆参数(-Xmx与-Xms)相关的设置,看看堆空间是否还有可调整的空间,如果有,可适当分配更多的堆空间内存。再从代码上检查 是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运 行期的内存消耗。

虚拟机栈和本地方法栈溢出:

1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError异常。

  • 《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机 的选择是不支持扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现 OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。

方法区和运行时常量池溢出:

  • 运行时常量池是方法区的一部分,在JDK 8中完全使用元空间来代替永久代。

  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生 成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。当创建的这类信息过多,填满了方法区空间的时候,就会造成oom。

  • 方法区的主要职责是用于存放类型的相关信息,如类 名、访问修饰符、常量池、字段描述、方法描述等。运行时如果产生大量的类去填满方法区,直到溢出就会产生OOM异常。方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,要达成的条件是比 较苛刻的。在经常运行时生成大量动态类的应用场景里,就应该特别关注这些类的回收状况。

  • 在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。在默认设置下, 正常的动态创建新类型的动作已经很难再迫使虚拟机产生方法区的溢出异常了。不过 为了让使用者有预防实际应用里出现的破坏性的操作,HotSpot还是提供了一 些参数作为元空间的防御措施,主要包括:

-XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存 大小。

-XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集 进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放 了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该 值。

-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可 减少因为元空间不足导致的垃圾收集的频率。

类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最 大的元空间剩余容量的百分比。

本机直接内存溢出:

直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不 去指定,则默认与Java堆最大值(由-Xmx指定一致,由直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了 DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。