记一次FullGC排查过程

一、背景

记一次FullGC排查过程_第1张图片
导入后,疯狂的进行fullGC

我们的回收算法是
年轻代:Parallel Scavenge,多线程,以吞吐量为准则
老年代:Parallel old ,多线程,但是全部stw.

我们平常用的应该是 Parallel Scavenge+CMS
记一次FullGC排查过程_第2张图片

二、排查过程

2.1 第一阶段:反正确认出现了full GC,确认GC的原因。

查询后得知:
如果晋升到老生代的平均大小大于老生代的剩余大小,则会返回true,认为需要一次full gc。

Ergonomics翻译成中文,一般都是“人体工程学”。在JVM中的垃圾收集器中的Ergonomics就是负责自动的调解gc暂停时间和吞吐量之间的平衡,然后你的虚拟机性能更好的一种做法

Parallel Scavenge的目标是达到一个可控的吞吐量,吞吐量=程序运行时间/(程序运行时间+GC时间),
如程序运行了99s,GC耗时1s,吞吐量=99/(99+1)=99%。
Parallel Scavenge提供了两个参数用以精确控制吞吐量,分别是用以控制最大GC停顿时间的
-XX:MaxGCPauseMillis及直接控制吞吐量的参数-XX:GCTimeRatio。

查看java进程的jvm配置:
jinfo -flags pid
image.png

2.2 第二阶段-回收内存不足

通过以上分析,总体看就是从堆的年轻代回收不了,老年代也回收不了。

那就先看下老年代和新生带为什么突然变大了?
我们重现一次场景,发现老年代确实是从初始化前的100M,上升到982M,无法回收
年轻代也是从163M可以回收,跑到最后无法回收保留163M。

(看圈口第一条,年轻代GC的时候,年轻代从163M回收到17M,总的堆内存从275M回收至113M,所以老年代就是大约113-17=100M)

但是我们的代码就做了一个导入,为什么会一直涨的内存不足呢?

堆打印出来分析:
记一次FullGC排查过程_第3张图片
记一次FullGC排查过程_第4张图片

最大的堆和类,都只有20M左右,我想不通为什么?这是为什么?

2.3 第三阶段:真正的大对象是什么?是POI对象

但是我特意注意到,我打堆信息的时候,有一次堆初始化,感觉是这个命令把堆给初始化了。
记一次FullGC排查过程_第5张图片

后面再去百度,不对劲,想起来我dump命令:
保留活的对象,意思就是只dump存活的对象,不会被GC的对象。
jmap -dump:live,format=b,file=heap2022120802.hprof 886651
保留所有对象
jmap -dump:format=b,file=heap2022120803.hprof 886651

后面看内存,确实存在1.8G,和实际的内存大小一致。再去用jprofile分析一下:可怜我的机器内存可能会不够,我先把不需要的软件全部关闭。

我们一般使用参数 live就是因为大部分情况是对象需要存活,不可回收,因此不足以GC足够的对象。

但是此时我们dump下,发现出现的问题是有很多回收的对象,但是依然回收不了,这是什么原因呢?

查看正常情况下堆的使用情况:
jmap -heap pid
老年代/新生代=2:1, eden:s0:s1=8:1:1 。但是实际情况不是这样的,再次还原现象查看堆的使用情况。
关于Eden:s0:s1=8:1:1 与实际 160M:160M:160M不一致问题。

VM默认使用 AdaptiveSizePolicy 策略自动分配Eden和Survivor空间大小。
所以下参数默认无效:
-XX:SurvivorRatio=8
使用以下参数,关闭AdaptiveSizePolicy,即可使Ratio生效:
-XX:-UseAdaptiveSizePolicy

Jdk 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy

记一次FullGC排查过程_第6张图片

记一次FullGC排查过程_第7张图片

不管怎样,至少确认,当前的堆就是用完了,无法回收。

再看jprofile堆内存的分析。好多对象都大于100M,这是怎么回事。先看最大的228M。
记一次FullGC排查过程_第8张图片

首先进入我们视野的就是:
org.aocahe.xmlbeans.impl.store.Xob E l e m e n t X o b j o r g . a o c a h e . x m l b e a n s . i m p l . s t o r e . X o b ElementXobj org.aocahe.xmlbeans.impl.store.Xob ElementXobjorg.aocahe.xmlbeans.impl.store.XobAttrXobj
同时这个文件只有124M的文字大小。就是那个Char对象。

不管怎样,罪魁祸首就是EasyExcel解析文件出现的大对象,但是怎么会导致这么大的对象呢,我们的EasyExcel号称内存小
查了EasyExcel的宣讲PPT。
记一次FullGC排查过程_第9张图片
记一次FullGC排查过程_第10张图片

不管怎样,我就觉的吹牛逼的可能性大一些。为了验证,我去导入一条小的文件进去。
image.png
发现即使是300K的Excel依然要回收近100M的数据,因此可以判断,是EasyExcel的问题。

2.4 解决办法1-升级EasyExcel

所以为了达到催牛逼的问题,我再去升级一下EasyExcel,看看牛逼行不行。
结果:还是不行,内存OOM。
记一次FullGC排查过程_第11张图片

2.5 解决办法2:日志

我怀疑是异常累加导致,又打了日志catch住。

但是还是看到内存回收不掉,但是不发生OOM。就是一直回收不了对象,释放不出来。
连错误日志都不行。

理论上我都花了老年代711M+480M新生代,怎么还解决不了呢。

2.6 解决办法3:调整新生代的比例

我再思考下,是不是应该调大新生代Eden的大小呢,因此我再去尝试将Eden:s0:s1调大生效试试看。
记一次FullGC排查过程_第12张图片
image.png调整生效,再次去场景重现。
我们发现一个有趣的现象,新生代其实是足够的,老年代不够。新生代需要往老年代走,但是老年代不足。
同时自动分配的新生代Eden:s0:s1,比自己分配的8:1:1更合理。

2.7 解决方法4:增加堆的大小

我们扩大我们的老年代内存,并且将自动配置策略取消。这次我直接增加至3G给Java。
这样新生代:老年代=1G:2G

GC正常,并且初始的时候,Eden占用1G=900M:50M:50M,后面变成了500M:200M:200M
老年代使用内存不停的上涨至1.5G. 快达到顶部了。

同时我们观察到数据库CPU飙升。哈哈,说明在插入数据库。

MYSQLcpu占用386%,java占用内存44%。

三、总结一下:

3.1、内存使用无上限,你开的多一些,GC次数少一些,开的少一些,GC次数多一些。但是无人如何,老年代内存必须足够,(新生代不足只是会触发高频GC,将内存往老年代赶)。

3.2、出现OOM,不一定有日志OOM。可能就是释放不了,无现象,就是假死。

3.3、熟悉堆命令,直接查看jvm参数,和堆实际的使用情况,比查看GC日志更快速定位,确认就是OOM。

因为使用占用到100%,就很明显了。
jmap -heap pid

3.4、堆的大小比例,还是用默认的好,8:1:1说实话不对劲,默认的自动调整策略更好用。

3.5 打堆信息,不要只打存活的信息

一般我们认为回收不掉肯定是因为不在GCroot上。因此回收不了,但是不知道为何,在我的工作经验中,很多次堆信息,都无法找出哪个对象最大,明明Java进程占用很大的内存,但是打出来都是很小的堆信息,导致无法分析问题。因此我建议不要只打存活的对象。

3.6、怎么确定堆的最大值和最小值。

最快的方法就是翻倍增加,因为内存很廉价。
严谨的做法就是压测,然后盯着GC日志,当GC的新生代和老年代内存稳定后,就是那个值了。比如下面的是:
809M新生代和1.7G的老年代。

四、jvm常用命令:

4.1、查看Java进程jvm配置

jinfo -flags pid

4.2、实时查看堆使用情况

jmap -heap pid

4.3、打印堆-不要只打存活的

保留活的对象,意思就是只dump存活的对象,不会被GC的对象。
jmap -dump:live,format=b,file=heap2022120802.hprof 886651
保留所有对象
jmap -dump:format=b,file=heap2022120803.hprof 886651

4.4、自动分配策略关闭

-XX:-UseAdaptiveSizePolicy

4.5 查看实时GC信息

jstat -gcutil 5 1000
每1秒输出一次日志,总共5次

4.5 什么场景出现GC?

现象 描述 解决方案
Full GC(Ergonomics) 新生代前几次往老年代移动平均需要的内存 大于 当前老年代的内存,触发一次老年代GC,主要是提前处理。动态平衡 1、快速解决办法:增加老年代内存
2、当然也可以想办法排查年轻代为什么这么频繁的有这么大的对象需要移动
3、调大堆的大小,同时调好堆的比例。新生代:老年代=1:2

|
| Full GC (Metadata GC Threshold) | 元区间(静态类区内,即以前的永久代)存不足,加载的类过多 | -XX:MetaspaceSize=128M
默认是20M左右,最大无上限 |
| concurrent mode failure | 也就是老年代正在清理,从年轻代晋升了新的对象,或者直接分配大对象年轻代放不下导致直接在老年代生成,这时候老年代也放不下 | CMS回收算法的 |
| promotion failed | 称为提升失败,是在进行 Minor GC时候,survivor space空间放不下只能晋升老年代,而此时老年代也空间不足时发生的 | 降低新生代的对象,增加老年代 |

你可能感兴趣的:(jvm,java,算法)