OOM异常实战
前言
在Java虚拟机除了程序计数器外,其他几个运行时内存区域都可能发生OOM,下面实例一下溢出场景,并分析。
Java堆溢出
重要参数
有几个重要参数设置如下:
-Xms 堆大小的最小值设置参数
-Xmx 堆的大小最大值,如果设置为最小值一样,则不允许自动扩展。
-XX:+HeapDumpOnOutOfMemoryError 让内存溢出时Dump出当前内存堆转储快照。
模拟产生方法
如果我们不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制回收这些对象,就能模拟抛出OOM异常的情景。
解决方案
使用内存映像分析工具:Eclipse Memory Analyzer对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,即要搞明白是内存泄漏还是内存溢出。
- 内存泄漏导致的OOM:new出来的很多对象已经不需要了,但仍然有引用指向,所以垃圾回收机制无法回收。
- 内存溢出:new出来的对象都是需要的,但堆内存太小装不下了。
如果是内存泄漏,通过工具查看泄漏对象到GC Roots的引用链。找到泄漏对象是通过怎样的路径与GC Roots发生关联,然后导致垃圾回收机制无法自动回收的。如果不存在内存泄漏,也就是所有的对象都必须存在,这时候就调大堆内存。
虚拟机栈和本地方法溢出
在hotspot中不区分虚拟机和本地方法栈,因此对于hotspot来说,-Xoss参数存在但是无效,栈容量只由-Xss参数决定。
书上的实验结果表明;
- 在单个线程下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError。
- 不限于单线程可以模拟出OOM,但是这种产生的内存溢出与栈空间足够大不存在联系,而且越大反而更容易产生内存溢出。原因:内存有限,每个线程分配到的栈容量越大,可以建立的线程数就越少,建立线程的时候就越容易把剩下的内存耗尽。
- 所以在多线程开发中特别注意,StackOverflowError有错误堆栈可以阅读,相对排查简单一些。大多数情况下达到1000~2000完全没有问题,对于正常的递归大多数情况下完全够用了。如果是多线程导致内存溢出的话,就只能减少最大堆或者栈的容量来获得更多的线程。
常量池溢出
模拟产生方法
如果要向运行时内存中添加内容,最简单的方式是使用string.intern()。
参数
-XX:PermSize -XX:MaxPermSize。溢出后提示信息是PermGen space。
方法区溢出
方法区存放class信息,比如类名、访问修饰符、常量池、字段描述、方法描述等。
模拟产生方法
产生许多类区填满方法区,直到溢出。还可以反射和动态代理,还可以用CGLIB直接操作字符码,生成动态类。(框架中精彩需要对类进行增强,都会使用到CGLIB,增强的类越多,就需要越大的方法区保证动态生成的class可以加载入内存)。
方法区溢出也是常见的内存溢出,一个类如果要被垃圾收集器回收掉条件是十分苛刻的。在动态生成大量Class文件的应用中,需要特别注意类的回收状况。常见的还有JSP应用。
直接内存溢出
参数
-XX:MaxDirectMemorySize,如果不指定默认和堆一样大。