新生代垃圾回收频繁GC

下图可以看出,新对象基本都是直接分配在eden区域

新生代垃圾回收频繁GC_第1张图片

分析快速增加的对象

借助于java自带的工具jmap -histo pid,可以快速多次获取虚拟机堆中当前各对象的实例数量以及占用内存大小。虽然获取内存dump文件也可以,但是耗时太长,另外机器可用内存太小,dump过程可能会有副作用。

数据(jmap -histo pid):

   1:      18541049     1693471216  [C
   2:       1464154      679431728  [B
   3:       7633015      520591584  [Ljava.lang.Object;
   4:      14029600      336710400  java.lang.String
   5:        293363      239603600  [I
   6:        741039      231204168  com.taobao.pamirs.punish.service.domain.PunishAwardRuleDO

gc以后数据(jmap -histo:live pid):

   1:       1733996      297797752  [C
   2:          2884      107472896  [J
   3:        206045      105961440  [B
   4:        709587       72891864  [Ljava.lang.Object;
   5:         66451       70673784  [I
   6:       1972519       63120608  java.util.HashMap$Node
   7:       2097152       50331648  com.alibaba.dts.client.executor.grid.queue.TaskEvent
   8:       1730310       41527440  java.lang.String
   9:        256403       31937688  [Ljava.util.HashMap$Node;
  10:        430317       30525112  [Lorg.h2.value.Value;
  11:        903184       21676416  java.lang.Long
  12:        385777       18517296  java.util.HashMap
  13:        223526       16093872  com.taobao.biz.common.division.GlobalDivisionVO
  14:        225538       14434432  com.taobao.forest.store.index.bst.BSCatPropIndexNode
  15:        600537       14412888  org.h2.value.ValueLong
  16:        574126       13779024  java.util.ArrayList
  17:        327510       10480320  java.util.concurrent.ConcurrentHashMap$Node
  18:        100054        8804752  java.lang.reflect.Method
  19:        320346        5125536  org.h2.value.ValueString
  20:         45185        5043432  java.lang.Class
  21:        200678        4816272  org.h2.value.ValueArray
  22:        198356        4760544  org.h2.mvstore.db.TransactionStore$VersionedValue
  23:         80812        4525472  com.taobao.hsf.remoting.service.HSFServiceURL
  24:         13515        4216680  com.taobao.pamirs.punish.service.domain.PunishAwardRuleDO

 

上述数据排除了耗用内存较少和实例个数较少的对象,以及已知占用内存较大的对象(比如forest相关对象)。

通过对比上述数据,发现在回收以后,PunishAwardRuleDO实例个数只有28910个(将近7MB),但是在之前的数据中,竟然有将近30万个实例(将近70MB),也就是有大量的对象成为垃圾对象被回收掉了,基本可以断定PunishAwardRuleDO存在问题,接下来开始翻代码:

新生代垃圾回收频繁GC_第2张图片

新生代垃圾回收频繁GC_第3张图片

新生代垃圾回收频繁GC_第4张图片

新生代垃圾回收频繁GC_第5张图片

翻过一遍代码以后,问题就比较明显了。在punishcenter-common-1.2.3-SNAPSHOT.jar包中,存在定时刷新所有的处罚规则的缓存,3分钟一次,缓存对象FileCache至少会存活3分钟时间,如果在这三分钟里面,young gc次数超过了15次(默认值,15次以后,对象会晋升到老年代),那么这个FileCache对象就会进入老年代,那么在后续的缓存切换以后,原有的缓存对象会继续留在老年代,变成垃圾对象,随着时间积累,内存中会存在越来越多的FileCache拷贝,只能等待被fullgc回收。

结合前面的指标,younggc本身也很频繁,3分钟超过15次还是很容易的。另外,结合其他同学提供的信息,在内存dump文件中,也发现FileCache存在多份拷贝,这样更加证实了这里存在问题。

2.上面的输出中[C对象占用Heap这么多,往往跟String有关,String其内部使用final char[]数组来保存数据的.

回收一次就可以从1.69G降到200M

 

总结

1、排除法,缩小问题范围(比如临时下线hsf,关闭nginx等排除QPS影响);
2、分析是否存在内存泄漏(分析内存使用曲线)
3、分析老年代新增对象(优先尝试轻量级工具 jmap -histo pid)
4、根据步骤3中找到的蛛丝马迹,拉取代码,深入分析
5、与相应团队沟通确认
6、拉分支,改代码,进行验证

说明:
步骤3常用的工具就是内存dump,然后使用zprofiler或者离线工具如mat进行分析,但是进行heap dump本身会触发一次gc操作,导致dump出来的对象已经没有垃圾对象了,不利于分析,但对于内存泄漏还是有用的。此外,这个操作本身很耗时,一次dump可能需要好几分钟,不利于快速分析。

因此,可以尝试另外一个工具,jmap -histo pid 和 jmap -histo:live pid,抓取内存中各对象的统计数据(直方图),主要包含实例个数,以及占用的内存空间。其中前一个命令,是抓取所有的对象,包括垃圾对象,而后一个命令只抓取存活对象的,结合这两个命令的输出,进行比对,能够快速找出垃圾对象信息。关键是,这个命令速度很快,可以快速进行多次操作。

最后,补充一点:
遇到此类问题(包括CPU过高问题),应用代码中的定时任务(非DTS定时任务)应该是第一怀疑对象,尤其是二方包里面的,因为这种任务会在所有机器上运行,最终拉高整个集群的指标。

附 - jmap输出中class name非自定义类的说明:

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L; reference an instance of class
S short signed short
Z boolean true or false
[ reference one array dimension

你可能感兴趣的:(JAVA)