论坛首页 Java企业应用论坛

如何定位OutOfMemory的根本原因

浏览 18937 次
精华帖 (0) :: 良好帖 (11) :: 新手帖 (6) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-07-19   最后修改:2011-07-21

自己最近做了一些关于工厂MES软件导致的OOM,比如aXXX,aXX,fXX公司, 这是给公司同事做的OutOfMemory定位问题的分享,垃圾回收是参考江南白衣的一篇博文,各个点都是些比较表层的sharing,还没有写完,也和广大同仁做个交流

1 分析工具

1)     动态分析工具

Jprofile下面的内存都是运行时的

 

 

1)     静态分析工具

a:  在启动java的时候加上参数 -XX:+HeapDumpOnOutOfMemoryError,这样如果由于OOM导致JVM crash的时候可以便于我们分析,生成的heap dump文件名字的命名规范如下, java_pidxxxx.hprof

b:  elcipsemat

     IBM heap ana:我们MR比较喜欢用这个IBM的工具进行OOM诊断,启动方式

java -Xmx1600 -jar ha396.jar,然后选择文件打开生成的heap dump.

2 Java 内存机制

1对于从事CC++程序开发的开发人员来说,担负着每一个对象生命开始到终结的维护责任。

     对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误。不过,也正是因为Java程序员把内存控制的权力交给了JVM,一旦出现泄漏和溢出,如果不了解JVM是怎样使用内存的,那排查错误将会是一件非常困难的事情。下面介绍一下java出现的OOM有关的 Exception和可能出现的方式

A Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

public static void main(String[] args) {

            //使用List保持着常量池引用,压制Full GC回收常量池行为

            List<String> list = new ArrayList<String>();

            // 10MPermSizeinteger范围内足够产生OOM

            int i = 0;

            while (true) {

                  list.add(String.valueOf(i++).intern());

            }

      }

这一部分用于存放ClassMeta的信息,Class在被 Load的时候被放入PermGen space区域(包括常量池: 静态变量,它和和存放InstanceHeap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APPLOAD很多CLASS的话,就很可能出现PermGen space动态生成的类,加载SpringHibernate对类进行增强时,都会使用到CGLib这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的Class可以加载入内存,以上是PermGen space区的常量池内存泄露导致的OOM.

 

B java.lang.OutOfMemoryError: Java heap space运行中产生的对象,被缓存的实例(Cache)对象,大的map,list引用大的对象等等,都会保存于此(Heap)

public static void main(String[] args) {

            List<String> list = new ArrayList<String>();

            int i = 0;

            while (true) {

                  list.add(new String(“test”));

            }

      }

 

 

C Exception in thread "main" java.lang.StackOverflowError

栈帧太多,也就是函数调用层级过多)导致。检查是否有死递归的情况~

/**

 * VM Args-Xss128k

 */

public class JavaVMStackSOF {

 

      private int stackLength = 1;

 

      public void stackLeak() {

            stackLength++;

            stackLeak();

      }

 

      public static void main(String[] args) throws Throwable {

            JavaVMStackSOF oom = new JavaVMStackSOF();

            try {

                  oom.stackLeak();

            } catch (Throwable e) {

                  System.out.println("stack length:" + oom.stackLength);

                  throw e;

            }

      }

}

 

2 Java 垃圾回收机制

基本回收算法

其实这些算法和OOM没有太大关系,OOM产生的原因很简单,就是需要内存的时候没有内存了,但是对垃圾回收机制的理解可以让我们知道-Xmx  -Xms Xmn – MaxPermSize参数该怎么样设置,避免频繁full-GC(Full-GC会造成app短暂停顿,此时app是不会响应任何客户端请求的),并且合理设置参数.1对于缓存过多的系统可以增大-Xmx ,通过减小Xmn调整新生代年老代比例,2对于瞬时对象过多的系统,年老代的heap可以不用分配那么大)

  1. 引用计数(Reference Counting
    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
  2. 标记-清除(Mark-Sweep
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
  3. 复制(Copying
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现碎片问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
  4. 标记-整理(Mark-Compact
    此算法结合了标记-清除复制两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象压缩到堆的其中一块,按顺序排放。此算法避免了标记-清除的碎片问题,同时也避免了复制算法的空间问题。
  5. 分代(Generational Collecting
    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。


分代垃圾回收详述

 

 

如上图所示,为Java堆中的各代分布。

  1. Young(年轻代)复制算法
    年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。
  2. Tenured(年老代)标记清除|标记整理
    年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。
  3. Perm(持久代)很难发生GC
    用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
  4. OOM导致的原因:Full GC后,发现剩余的(heap,方法区)空间还是不够程序使用,这样就会导致OOM,  能导致Full GC
    1. Tenured被写满
    2. Perm域被写满
    3. System.gc()被显示调用
    4. 上一次GC之后Heap的各域分配策略动态变化

 

3实战演练

背景介绍

1这个是XXX 的一个Support Case,他们用我们XXX的平台软件开发了一个从ERP系统导入订单到我们系统的一个转换程序,他们把ERP系统的所有订单写到一个txt文件里面。然后用他们的写的应用来转换txt中的单信息到我们系统的Lot, workorder对象等等。但是他们发现有个问题,就是导入的txt文件如果过大的话,就会有可能报出OOM异常,这个对他们影响是很大的,不但造成程序crash,而且他们不知道当前导入了多少order,没法回滚脏数据。于是Support建议他们加大Client端的内存(比如-Xmx 2G),但是这样还是会有问题的,因为你不知道从ERP导出的txt的文件有多大,所以你不知道要设置多大的内存才是合适的.

2

从下面这张图这里可以让大家猜测大概是什么问题导致的内存泄露

 

 

 

 

这个是用 IBM heap ana分析heap dump的视图,大家可以看一下,96%内存都在pnuts/lang/context中,其中92%内存是被lot对象占用的,lot对象保存到Vector中,所以是由于lot对象保存在Vector中,但是vector没有及时释放这些对象导致的。所以可以通过vector.addElement去全文检索,看看是否有使用的vector缓存没有及时释放的地方。

 

3实际内存泄露的代码.(绿色标注的那行)

Please locate the lines of code around line 2736 of Subroutine ImportWoFile. Following is the some pieces of the codes about it:

 

foreach orderItem(vecOrderItems.elements())

{

….

newLot = createLot(strLotName, workOrder)

newLot.save()

if (blnSaveOK == false)

        {

           ……

 

        } else

        {

  //Actually leak script           vecLot.addElement(newLot)

            ….

        }

}

最后搜索程序,发现vecLot的句柄一直被context全局类持有,这样看来,由于导入的txt文件过大(产生对象过多),很容易发生OOM

Obviously, the lot of vectLot will be tremendous if they want to import a large wo file at one time. So it will consume the memory of JVM.

 

 

 

   发表时间:2011-07-19  
好文必顶~
0 请登录后投票
   发表时间:2011-07-20  
可以参考一下淘宝的林昊写的<Java 分布式开发与实践> .里面对于jvm的内存模型以及调优解释得还是蛮清楚的
0 请登录后投票
   发表时间:2011-07-20  
刚看了开头以为是一篇好文,浏览到最后总结:理论部分网上到处都有,实战部分不仅写得少,而且没写清楚
0 请登录后投票
   发表时间:2011-07-20  
apei830 写道
Roy,必须顶啊

低调低调
0 请登录后投票
   发表时间:2011-07-20  
richard_2010 写道
刚看了开头以为是一篇好文,浏览到最后总结:理论部分网上到处都有,实战部分不仅写得少,而且没写清楚

其实公司的分享还没有做,还是初稿,会慢慢补全,谢谢建议
0 请登录后投票
   发表时间:2011-07-20  
深层递归调用很容易产生oom
0 请登录后投票
   发表时间:2011-07-20  
写的东西没啥实质性自己的东西,网上东拼西凑的,如果能写出的自己的实战经验
0 请登录后投票
   发表时间:2011-07-20  
不错,最近我也在学习服务器优化方面资料
0 请登录后投票
   发表时间:2011-07-21  
很好的文章,顶
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics