zoukankan      html  css  js  c++  java
  • full gc频繁的分析及解决案例

    full gc频繁的分析及解决案例
    2016-04-14 09:20:54      0个评论    来源:end's coding life  
    收藏   我要投稿

    现象

    1
    系统报警full gc次数过多,每2分钟达到了56次,这是不正常的现象
    1
    在full gc报警时的gc.log如下:
    1
     
    1
    在full gc报警时的jstat如下:
    1
    sudo -u admin -H /opt/taobao/java/bin/jstat -gcutil `pgrep java` 2000 100
    1
     
    1
    此时的cpu如下(基本都是在做gc):
    1
     
    1
    将应用重启后,问题解决
    1
    但是当后台执行低价航线更新时,过大概十几个小时后,又出现上述情况!

    分析

    1
    当频繁full gc时,jstack打印出堆栈信息如下:
    1
    sudo -u admin -H /opt/taobao/java/bin/jstack `pgrep java` > #your file path#
    1
     
    1
    可以看到的确是在跑低价信息
    1
    另外在应用频繁full gc时和应用正常时,也执行了如下2种命令:
    1
    sudo -u admin -H /opt/taobao/java/bin/jmap -histo `pgrep` > #your file path#
    1
    sudo -u admin -H /opt/taobao/java/bin/jmap -histo:live `pgrep` > #your file path#(live会产生full gc)
    1
    目的是确认以下2种信息:
    1
    1)是否存在某些引用的不正常,造成对象始终可达而无法回收(Java中的内存泄漏)
    1
    2
    2)是否真是由于在频繁full gc时同时又有大量请求进入分配内存从而处理不过来,
            造成concurrent mode failure?
    1
    下图是在应用正常情况下,jmap不加live,产生的histo信息:
    1
     
    1
    下图是在应用正常情况下,jmap加live,产生的histo信息:
    1
     
    1
    下图是在应用频繁full gc情况下,jmap不加live和加live,产生的histo信息:
    1
     
    1
    从上述几个图中可以看到:
    1
    1)在应用正常情况下,图中标红的对象是被回收的,因此不是内存泄漏问题
    1
    2
    2)在应用频繁full gc时,标红的对象即使加live也是未被回收的,因上就是在频繁full gc时,
            同时又有大量请求进入分配内存从而处理不过来的问题

    先从解决问题的角度,看怎样造成频繁的full gc?

    从分析CMS GC开始
    1
    先给个CMS GC的概况:
    1
    1)young gc
    1
    可以看到,当eden满时,young gc使用的是ParNew收集器
    1
    ParNew: 2230361K->129028K(2403008K), 0.2363650 secs解释:
    1
    1)2230361K->129028K,指回收前后eden+s1(或s2)大小
    1
    2)2403008K,指可用的young代的大小,即eden+s1(或s2)
    1
    30.2363650 secs,指消耗时间
    1
    2324774K->223451K(3975872K), 0.2366810 sec解释:
    1
    2
    1)2335109K->140198K,指整个堆大小的变化
    (heap=(young+old)+perm;young=eden+s1+s2;s1=s2=young/(survivor ratio+2))
    1
    20.2366810 sec,指消耗时间
    1
    [Times: user=0.60 sys=0.02, real=0.24 secs]解释:指用户时间,系统时间,真实时间
    1
     
    1
    2)cms gc
    1
    当使用CMS收集器时,当开始进行收集时,old代的收集过程如下所示:
    1
    2
    a)首先jvm根据-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly
         来决定什么时间开始垃圾收集
    1
    2
    b)如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了
         -XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc
    1
    2
    3
    c)如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定什么时候
        触发cms gc;因此有时会遇到设置了80%比例才cms gc,但是50%时就已经触发了,就是因为这个参数
        没有设置的原因
    1
    2
    d)当cms gc开始时,首先的阶段是CMS-initial-mark,此阶段是初始标记阶段,是stop the world阶段,
         因此此阶段标记的对象只是从root集最直接可达的对象
    1
    CMS-initial-mark:961330K(1572864K),指标记时,old代的已用空间和总空间
    1
    2
    e)下一个阶段是CMS-concurrent-mark,此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,
         主要作用是标记可达的对象
    1
    此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark
    1
    2
    f)下一个阶段是CMS-concurrent-preclean,此阶段主要是进行一些预清理,因为标记和应用线程是并发执行的,
        因此会有些对象的状态在标记后会改变,此阶段正是解决这个问题
    1
    2
    因为之后的Rescan阶段也会stop the world,为了使暂停的时间尽可能的小,也需要preclean阶段先做一部分
        工作以节省时间
    1
    此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
    1
    2
    g)下一阶段是CMS-concurrent-abortable-preclean阶段,加入此阶段的目的是使cms gc更加可控一些,
         作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间
    1
    此阶段涉及几个参数:
    1
    -XX:CMSMaxAbortablePrecleanTime:当abortable-preclean阶段执行达到这个时间时才会结束
    1
    2
    -XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制abortable-preclean阶段什么时候开始执行,
    即当eden使用达到此值时,才会开始abortable-preclean阶段
    1
    -XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制abortable-preclean阶段什么时候结束执行
    1
    此阶段会打印一些日志如下:
    1
    2
    CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,
    CMS:abort preclean due to time XXX
    1
    2
    h)再下一个阶段是第二个stop the world阶段了,即Rescan阶段,此阶段暂停应用线程,对对象进行重新扫描并
         标记
    1
    YG occupancy:964861K(2403008K),指执行时young代的情况
    1
    CMS remark:961330K(1572864K),指执行时old代的情况
    1
    此外,还打印出了弱引用处理、类卸载等过程的耗时
    1
    i)再下一个阶段是CMS-concurrent-sweep,进行并发的垃圾清理
    1
    j)最后是CMS-concurrent-reset,为下一次cms gc重置相关数据结构
    1
     
    1
    3)full gc:
    1
    2种情况会触发full gc,在full gc时,整个应用会暂停
    1
    a)concurrent-mode-failure:当cms gc正进行时,此时有新的对象要进行old代,但是old代空间不足造成的
    1
    2
    b)promotion-failed:当进行young gc时,有部分young代对象仍然可用,但是S1或S2放不下,
        因此需要放到old代,但此时old代空间无法容纳此
    1
     
    频繁full gc的原因
    1
    2
    从日志中可以看出有大量的concurrent-mode-failure,因此正是当cms gc进行时,有新的对象要进行old代,
    但是old代空间不足造成的full gc
    1
    进程的jvm参数如下所示:
    1
     
    1
    影响cms gc时长及触发的参数是以下2个:
    1
    -XX:CMSMaxAbortablePrecleanTime=5000
    1
    -XX:CMSInitiatingOccupancyFraction=80
    1
    解决也是针对这两个参数来的
    1
    根本的原因是每次请求消耗的内存量过大

    解决

    1
    2
    1)针对cms gc的触发阶段,调整-XX:CMSInitiatingOccupancyFraction=50,提早触发cms gc,就可以
            缓解当old代达到80%,cms gc处理不完,从而造成concurrent mode failure引发full gc
    1
    2
    2)修改-XX:CMSMaxAbortablePrecleanTime=500,缩小CMS-concurrent-abortable-preclean阶段
            的时间
    1
    2
    3
    3)考虑到cms gc时不会进行compact,因此加入-XX:+UseCMSCompactAtFullCollection
           (cms gc后会进行内存的compact)和-XX:CMSFullGCsBeforeCompaction=4
           (在full gc4次后会进行compact)参数
    1
    但是运行了一段时间后,只不过时间更长了,又会出现频繁full gc
    1
    计算了一下heap各个代的大小(可以用jmap -heap查看):
    1
    total heap=young+old=4096m
    1
    perm:256m
    1
    young=s1+s2+eden=2560m
    1
    young avail=eden+s1=2133.375+213.3125=2346.6875m
    1
    s1=2560/(10+1+1)=213.3125m
    1
    s2=s1
    1
    eden=2133.375m
    1
    old=1536m
    1
    2
    可以看到eden大于old,在极端情况下(young区的所有对象全都要进入到old时,就会触发full gc),
    因此在应用频繁full gc时,很有可能old代是不够用的,因此想到将old代加大,young代减小
    1
    改成以下:
    1
    -Xmn1920m
    1
    新的各代大小:
    1
    total heap=young+old=4096m
    1
    perm:256m
    1
    young=s1+s2+eden=1920m
    1
    young avail=eden+s1=2133.375+213.3125=1760m
    1
    s1=1760/(10+1+1)=160m
    1
    s2=s1
    1
    eden=1600m
    1
    old=2176m
    1
    此时的eden小于old,可以缓解一些问题
    1
     
    1
    改完之后,运行了2天,问题解决,未频繁报full gc
  • 相关阅读:
    java加载类的方法1.classloader 2.class.forName()
    servlet与线程与jdbc connection的关系
    static再次深入理解
    多线程读某个共享变量有时候也要给读方法加锁
    多线程读一个全局变量要不要加锁?还是说只是当修改全局变量的时候才要加锁?
    接口耗时打印并统计
    Java从设计模式[本场比赛状态转换武器]状态分析(State)模式
    Openstack中间DVR Part1 -- 东西走向的交通处理
    写酷“大神”的公开信
    从反思谈论阵列和指针的几个问题,腾讯的笔名
  • 原文地址:https://www.cnblogs.com/lupeng2010/p/8252970.html
Copyright © 2011-2022 走看看