OOM异常实战

OOM异常实战

前言

       在Java虚拟机除了程序计数器外,其他几个运行时内存区域都可能发生OOM,下面实例一下溢出场景,并分析。

Java堆溢出

重要参数

       有几个重要参数设置如下:

  1. -Xms 堆大小的最小值设置参数

  2. -Xmx 堆的大小最大值,如果设置为最小值一样,则不允许自动扩展。

  3. -XX:+HeapDumpOnOutOfMemoryError 让内存溢出时Dump出当前内存堆转储快照。

模拟产生方法

       如果我们不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制回收这些对象,就能模拟抛出OOM异常的情景。

解决方案

       
使用内存映像分析工具:Eclipse Memory Analyzer对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,即要搞明白是内存泄漏还是内存溢出。

  1. 内存泄漏导致的OOM:new出来的很多对象已经不需要了,但仍然有引用指向,所以垃圾回收机制无法回收。
  2. 内存溢出:new出来的对象都是需要的,但堆内存太小装不下了。

       如果是内存泄漏,通过工具查看泄漏对象到GC Roots的引用链。找到泄漏对象是通过怎样的路径与GC Roots发生关联,然后导致垃圾回收机制无法自动回收的。如果不存在内存泄漏,也就是所有的对象都必须存在,这时候就调大堆内存。

虚拟机栈和本地方法溢出

       在hotspot中不区分虚拟机和本地方法栈,因此对于hotspot来说,-Xoss参数存在但是无效,栈容量只由-Xss参数决定。

       书上的实验结果表明;

  1. 在单个线程下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError。
  2. 不限于单线程可以模拟出OOM,但是这种产生的内存溢出与栈空间足够大不存在联系,而且越大反而更容易产生内存溢出。原因:内存有限,每个线程分配到的栈容量越大,可以建立的线程数就越少,建立线程的时候就越容易把剩下的内存耗尽。
  3. 所以在多线程开发中特别注意,StackOverflowError有错误堆栈可以阅读,相对排查简单一些。大多数情况下达到1000~2000完全没有问题,对于正常的递归大多数情况下完全够用了。如果是多线程导致内存溢出的话,就只能减少最大堆或者栈的容量来获得更多的线程。

常量池溢出

模拟产生方法

       如果要向运行时内存中添加内容,最简单的方式是使用string.intern()。

参数

              -XX:PermSize                      -XX:MaxPermSize。溢出后提示信息是PermGen space。

方法区溢出

       方法区存放class信息,比如类名、访问修饰符、常量池、字段描述、方法描述等。

模拟产生方法

       产生许多类区填满方法区,直到溢出。还可以反射和动态代理,还可以用CGLIB直接操作字符码,生成动态类。(框架中精彩需要对类进行增强,都会使用到CGLIB,增强的类越多,就需要越大的方法区保证动态生成的class可以加载入内存)。

       方法区溢出也是常见的内存溢出,一个类如果要被垃圾收集器回收掉条件是十分苛刻的。在动态生成大量Class文件的应用中,需要特别注意类的回收状况。常见的还有JSP应用。

直接内存溢出

参数

-XX:MaxDirectMemorySize,如果不指定默认和堆一样大。

坚持原创技术分享,您的支持将鼓励我继续创作!