JVM老年代异常连续波动问题分析排查总结

一. 问题背景

通过公司系统监控发现线上环境一服务的老年代使用情况上下波动很大,时不时会触发高使用率报警。这里针对问题的分析和处理,做一个简单的回顾和总结。

垃圾收集器:G1,jdk环境:jdk8.

二. 排查过程

1. 监控表现

收到告警后,先去看了一下对应机器的JVM情况,可以发现JVM Mem Heap used中,老年代每隔几十分钟就会触发一次垃圾回收,然后很快占用率又会很快上涨到一个很高的水平.


而对比大部分正常的应用,老年代的占用相对都是比较平稳的,波动也不会那么剧烈.下面是一个正常应用的老年代生长曲线,和上图对比十分明显.


再看一下问题应用的GC情况,其中大对象分配(G1 Humongous Allocation)触发的Mixed GC非常规律,基本和老年代回收的节奏相匹配。


2. 问题推测

在机器上查看一下JVM的配置参数,发现region配置的大小为4M,那么超过2M的对象就会直接分配到老年代。通过监控初步推测,是G1垃圾收集器将大对象(超过region大小50%的对象)直接分配到老年代的region上,推测有可能存在周期性分配大对象直接到老年代,进而导致老年代不断上涨。

而老年代上涨的另外一种情况是年轻代对象在经过若干次survivor间的复制之后仍然未被young gc掉,最终被promoted到老年代,但是这种情况一般不会伴随Humongous Allocation。另外,通过监控也可以看到,问题应用的老年代上涨,主要就来源于allocated(大对象分配),所以可以把promoted的原因排除掉。


3. 工具使用

在有了初步的推测后,下一步就是有要看一下堆里面到底有些什么东西,这里自然少不了heap dump日志文件。

(1)定位应用程序的pid

在有问题的机器上查看应用程序的pid:

 ps -ef | grep java

(2)研发自主进入机器上打dump:

在lalaplat2容器页面上,登录到webterminal打dump,多个容器先在第一个容器上执行,指定生成目录到/tmp/下.

jmap -dump:format=b,file=/tmp/dump.hprof pid
参考: jmap -dump:format=b,file=/tmp/dump.hprof 1

补充说明:

map的dump参数有个live选项,加了live是指定dump内存中存活对象的快照,会强制触发一次full gc。不加的话则会dump堆中所有对象的快照。这次问题并未涉及到内存泄漏,而是想确认清楚到底是什么对象占据了大量老年代,因此没有加live参数,保留了内存中所有的对象。

(3)分析工具的选取

拿到dump文件,接下来就是要找一个趁手的工具去分析,我这边选用的是MAT.(Memory Analyzer)

说明:这里我使用的是1.11的版本,jdk8,内存设置4g。特别注意:使用MAT分析大的dump文件,需要调整MAT内存大小的。

关于MAT这里面需要特别提一下的是,可能有些同学有疑问,明明dump下来的文件非常大(比如对应的hprof文件有1.9G),或者监控上当时显示heap已经占用了好几个G,为什么工具上size显示的只有69.6M?

对于MAT来说,size默认展示的是存活对象的大小,那些在GC root上无法触达的对象(unreached)是没有计入的,一般来说这些对象是可以被垃圾回收器正常回收的,不是问题的关键所在,没有必要过多去关注。

可以看到截图上有313个unreachable objects,点进去一共有1.6个G左右。对unreachable objects的处理,其他工具如JProfiler也是类似。
当然你也可以选择在Preferences中把unreachable objects展示出来,如下图所示:


之后关闭MAT再重新加载hprof文件就可以看到包含unreachable objects的堆大小了。


特别说明:如果要查看unreachable objects的具体list明细value内容,是需要开启上述配置的,要不然是看不到的。

4. 问题定位

再回到老年代告警问题本身,我们打开Histogram来看一下堆上都有哪些巨大的对象。

可以看到有421w多char[]数组占据了大约670M以上的堆空间,64w多byte[]数组占据了356M以上的堆空间。分别点进outgoing reference去看一下具体的对象,可以看到每个char[]和byte[]对象都非常的大,占用了2M多的空间。

将value拷贝出来放到文本文件中,发现内容是Prometheus上报的监控数据,和/actuator/prometheus接口返回的内容是一致的。

这样就基本定位到了老年代上涨的"元凶"。仔细看了一下,monitor采集的指标数据都是正常的,不存在某个metric的label部分出现上千种组合而引发TooManyValue的情况。

三. 问题结论:

1. 问题根因:

G1HeapRegionSize设置不合理,而prometheus采集的指标数据超过region的50%引起的。

2. 解决方案:

加大region的大小,设置-XX:G1HeapRegionSize=8M。

注意:

(1)这个参数需要设置为2的幂次方,最小值是1M,最大值是32M。

另外,如果可以的话增加JVM堆大小(即-Xmx -Xms),这里是只采用了增加region大小的方式。

(2)添加配置中,注意参数的先后顺序。【实战小记】java启动参数JAVA_OPT不生效问题 - Justtear - 博客园


下面是PRE环境调整region大小前后的对比监控曲线:

调整前:

调整后:

你可能感兴趣的:(JVM老年代异常连续波动问题分析排查总结)