记一次线上频繁GC的排查过程

背景

公司有个给服务商使用的直营系统,用户不算多,但是服务器经常出现报警,CPU过高,但是很快CPU又恢复了下来。运维的同事看不下去了,叫我看看,虽然不会影响使用,但是看着频繁的报警实在太烦了。

排查过程

了解现状

系统是用法java做的开发,运行环境是jdk8,应用的JVM配置参数具体信息见下图。

 

 

第一时间就想到了是GC导致的CPU突然变高。为了验证想法,去服务器上看了一下。

 

本想通过TOP命令获取到占用CPU较高的线程,再去看看对应线程的堆栈信息。但是CPU基本都是不到 1%,看了也找不到重点相关线程。还是直接打印GC的信息出来吧,不看不知道,一看吓一跳!

 

可以看到metaspace(元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm)的使用比例是95%,YGC 6103 次,FGC 1506 次。可怕的是FGC占用的时间高达1314.421s,因为这21分钟java应用进程是STW(stop the world,类似暂停,不可用)。

看了一下内存的分配。 

配合之前jvm配置信息,跟内存分配,我以为是没主动给应用分配的内存,自动分配的内存太小了,才导致的频繁GC。问了一下运维的同学,运维的同学回答说,“因为zhiying 长期吃掉主机资源. 我不得不对他进行限制.保护其他资源”,具体的做法就是对docker的资源进行限制。

寻找问题代码

好了,那不管配置多少资源,最后都是会被吃掉,那就是代码的问题了。我就dump了堆内存信息,并下载到本机用eclipse打开看看究竟是哪些对象占用了较多的内存。

jmap -dump:live,format=b,file=heap.hprof  16671

看了很久,不过一些java的原始数据跟对象,没有涉及到业务对象。 那就看一下gc的原因吧

jstat -gccause 16671 1000

OMG,该不会是代码里面有人直接调用System.gc()吧?

// help gc
            if (list.size() > 10000) {
                list = null;
                System.gc();
            }

help gc ? help me please! 我们知道:

  • System.gc其实是做一次full gc
  • System.gc会暂停整个进程
  • System.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC

分析与解决

问题代码已经找到了,那就下来就是分析修改了。我们来看看为什么会有这样的代码出现

List<Object[]> list = (List<Object[]>) jpaQuery.list(em);

// 统计数量
list.parallelStream().forEach(colums -> {
TaskType taskType = (TaskType) colums[1];
TaskStatus taskStatus = (TaskStatus) colums[2];
Boolean trunkEndFlag = (Boolean) colums[3];

......

好家伙,原来是为了要统计数据,先把数据全部查出来,然后遍历数据分类统计。 又意识到这样会有生成很多大对象(多的话会有几十万数据放在这个List对象),所以就手动做垃圾回收。 好吧,既然能有垃圾回收的概念,为什么就不能转变一下思路了,sql直接统计出来数据,那样就不会生成大对象了。对,解决方案就是修改业务统计的逻辑。

总结

默认情况下,System.gc()会显式直接触发Full GC,同时对老年代和新生代进行回收。而一般情况下我们认为,垃圾回收应该是自动进行的,无需手工触发。如果过于频繁地触发垃圾回收对系统性能是没有好处的。因此虚拟机提供了一个参数DisableExplicitGC来控制是否手工触发GC。应该从根源解决对象生成的问题,而不是要生成之后再去回收,这样很耗CPU。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值