CTS/GTS 问题分析10 | weiinter105

一个CTS框架自身引起的问题

问题初探

测试命令: run retry --retry 1 --shard-count 6 -s 45f971f -s 5b01edc -s 9485acea -s 9485acda -s 948bac82 -s 948bac62

报错堆栈:


 

首先,这是一个频繁full gc,但是并没有回收多少内存造成的报错,我们将gc日志也打出来

尝试将tools中的cts-tradefed里面改为

 

重新复现,打出如下log:

 

GC overhead limt exceed检查是Hotspot VM 1.6定义的一个策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。“

而我们发现在抛出异常之前,确实频繁出现了full gc的日志,下面选取两个典型日志看一下含义:

[GC (Allocation Failure) [PSYoungGen: 1350980K->26096K(1204736K)] 1415587K->304733K(1483776K), 0.1134238 secs] [Times: user=0.70 sys=0.10, real=0.11 secs]
这个属于minor gc的日志:

  1. GC表示这是一次Minor GC(新生代垃圾收集)

2.[PSYoungGen: 1350980K->26096K(1204736K)] PSYoungGen表示新生代使用的是多线程垃圾收集器Parallel Scavenge,1350980K为gc前新生代占用空间的大小,26096K表示gc后占用空间的大小,
1204736K为新生代总大小
注:HotSpot JVM把新生代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

3.1415587K->304733K(1483776K) 1415587K: gc前堆占用总空间 304733K: gc后堆占用总空间 1483776K:当前堆的总大小
由()中的两个值可以算出老年代的大小,如本例中为: 1483776K - 1204736K

4.0.1134238 secs 本次gc消耗时间

  1. [Times: user=0.70 sys=0.10, real=0.11 secs]

user- 表示在此次垃圾回收过程中,所有GC线程消耗的CPU时间之和
sys - 表示GC过程中操作系统调用和系统等等事件所消耗的时间
real - 应用暂停的总时间。在并行GC中,这个数值应该接近于(user + sys) / GC线程数。即单个核上平均的暂停时间 。由于某些过程是不能并行执行的,所以这个值会比刚才求的均值略高

[Full GC (Ergonomics) [PSYoungGen: 465920K->465903K(931840K)] [ParOldGen: 2796435K->2796435K(2796544K)] 3262355K->3262339K(3728384K), [Metaspace: 70019K->70015K(1112064K)], 1.5992428 secs] [Times: user=11.32 sys=0.00, real=1.59 secs]

代表full gc,大体的格式类同,以三个数字为一组,分别代表新生代,老年代,总堆以及元数据区;从上面的log可以看出,报错前的full gc耗时1.5s,但并没有回收多少空间,最后导致错误:

问题分析

从log中我们可以看到这一行log,Dumping heap to java_pid14450.hprof ... 那么就可以用MAT分析得到的hprof文件

Path To GC Roots -> exclude weak/soft references

从上面的图中可以看到CommandScheduler中的InvocationThread中的run方法中持有大量的局部变量会导致最终的gc overhead limit exceceeded.注意: Histogram是从类的角度来总结对上内存分配状况,Dominator Tree可以是从实例的角度来总结,Path To GC Roots 可以看到引用路径,导致对象没有被gc的原因

大致的引用路径如下:

 

 

 

 

 

最终导致持有大量的CompatibilityTestSuite,导致case fail不能6台case一起retry

问题总结

因为是属于google的cts测试框架问题,所以先向google反馈;临时的解决方案,减少retry机器台数,将-Xmx4g改为-Xmx8g

 

环境配置

在上面的临时修改之后,可以在运行时抓一下hprof文件与gc overhead时产生的文件进行对比,但是可能需要一些环境配置,这里记录下我遇到的一些问题,如要查看的是pid = 8154

sudo jmap -heap 8154

结果如下:

 

因为这里用到了sun.jvm下的debug包,默认openJDK的安装中不包含,要么装个oracle的jdk,要么下载一个openjdk的debuginfo,这里安装正式jdk

1.官网下载压缩文件

  1. 手动解压配置

sudo update-alternatives --install /usr/bin/java java /home/weijuncheng/java8/jdk1.8.0_191/bin/java 700
sudo update-alternatives --install /usr/bin/javac javac /home/weijuncheng/java8/jdk1.8.0_191/bin/javac 700
sudo update-alternatives --install /usr/bin/jar jar /home/weijuncheng/java8/jdk1.8.0_191/bin/jar 700

  1. sudo update-alternatives --config java 切换到jdk 1.8

然后又报错了

 

原因:
cd /etc/alternatives

lrwxrwxrwx 1 root root 45 11月 1 14:41 java -> /home/weijuncheng/java8/jdk1.8.0_191/bin/java
lrwxrwxrwx 1 root root 42 1月 22 2018 jmap -> /usr/lib/jvm/java-8-openjdk-amd64/bin/jmap

两者不一样,所以会报版本不同的错误

sudo update-alternatives --config jmap
选择jdk 1.8

再执行,则没有报错了

最后jmap -dump:live,format=b,file=heap.hprof pid即可为相应pid生成hprof文件

作者: RESSRC

个人资源站

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据