奇妙的GC案例分享两则

一、20秒的young gc了解一下

开门见山↓,

图1

惊讶之余,我们一起探探究竟。

首先要会看gc log,一图胜千言↓

奇妙的GC案例分享两则_第1张图片

图2

有个细节很关键,user(用户耗时)+sys(系统耗时)

再来看下,每个耗时的具体口径↓

1、real time:GC事件整个过程自然流逝的绝对时间,这个跟钟表上的时间是一致的。(如果GC从10点开始,10点30结束,real time就是30分钟)。

2、user time:cpu花在用户态的时间。

3、sys time:cpu花在内核态的时间,也就是说内核发生系统调用所花费的时间。

大多数的young GC中,real time是小于sys+user time的,因为paralle垃圾回收器是多线程并发的去GC,所以user time是各个线程累积的一个时间,大概率要大于real time。

但是图1的case,是什么情况呢?有两种可能↓

1.CPU不够用

当cpu非常繁忙,进程和线程都比较多,写gc log的线程,抢不到cpu时间片的时候,real time显然就比sys+user时间长了。

可以用top命令来查看服务器的负载情况,如果cpu的使用率非常高,可以这样↓

(1)减少服务器上运行的进程数,让重要的进程可以获得足够的cpu资源。

(2)合理减少进程内的线程数,降低并行度。

(3)把应用部署到有足够cpu的服务器上。

2.IO压力大

应用做GC日志打印的时候,也需要访问磁盘。当磁盘的压力非常重的时候,GC事件就有可能被阻塞,这会导致real time变长。

注意:就算不是你的应用导致的磁盘负载重,如果服务器上其他的应用导致的磁盘压力也会导致real time变长。

可以用如下命令来监控服务器的磁盘负载情况:

sar -d -p 1

这个命令会输出每秒钟磁盘的读写数量。

如果你发现服务器的磁盘压力非常大,那么可以考虑下面的方法来解决:

(1)如果是我们的应用引起的,可以优化代码,减少不必要的io操作,也可以合并部分io操作,批量化。

(2)把应用部署到另一台磁盘负载不高的机器上。

 

二、聊聊cms GC中的concurrent mode failure

在上一篇,耗时20多秒的young gc,你见过吗? 的结尾,给大家预告了一个有趣的case,现在开始分享一下。

 

直接上图↓

奇妙的GC案例分享两则_第2张图片

图1

从红框1中,可以看到cms full gc开始进行初始化标记了,紧接着在红框二处,开始了并发标记,说到这里,再贴个好图,帮大家回忆一下cms的几个阶段↓

奇妙的GC案例分享两则_第3张图片

图2

言归正传,图1红框3中,可以看到,有7次由于新生代空间不足,导致allocation failure引起的young gc,然后再图1红框4中,可以看到,此时jvm堆空间也已经使用了3760040K,也就是3.6G,而整个堆只有4G。

 

敲黑板,重点来了,当young gc的时候,把eden和survivor里的都还存活的对象,统一移到另一个survivor区中时,发现装不下了,就需要把部分对象,放到老年代中去,结果老年代空间也不足,这种场景呢,叫做promotion failed

 

在promotion failed的前提下,老年代恰好还正在full gc,那么就会有图1红框5中的字样提示,concurrent mode failure。

 

在图一红框6中,可以看到整个堆的使用空间从4032680K(3.8G),变成了868795K(0.8G),这是一次真实耗时3.77秒,会有STW现象的full gc ,也叫做备份gc。

 

有的同学可能会疑惑,这个full gc有点慢呀, 是的。其中一个很大的原因,就是concurrent mode failure情况下的full gc使用的并不是cms的垃圾回收器,而是使用的Serail-Old垃圾回收器,全程是单线程串行操作, 肯定比较慢。

 

为啥concurrent mode failure了,就要退化成Serail-Old呢 ?来看看R大的回答↓

奇妙的GC案例分享两则_第4张图片

 

可是,不是有一个ParallelOld回收器吗?直接用呗,为啥非要用serial-old呢?原因是这个回收器与CMS GC不兼容所以无法作为它的备份full GC使用。

 

说完了原因,再来看看解决方案,从两方面着手,首先是排查应用程序的内存泄漏问题,看看是否有频繁且大量的不必要的创建对象的操作,再就是评估下当前的系统负载和容量情况,看是否需要扩容调整。

 

如果程序代码排查没问题、容量也没问题,那就要进行jvm参数的调优了,加上-XX:UseCMSCompactAtFullCollection参数来减少内存碎片,本文中的情况是新生代较小(只有300M),因此还有很多对象晋升到老年代,于是老年代内存使用量也涨的比较快,所以可以适当通过-XX:NewSize和-XX:MaxNewSize来调整下新生代的大小。

 

如果新生代大小是合理的话,则可以调大老年代的容量,如果内存有限的话,可以适当调小XX:CMSInitiatingOccupancyFraction参数,让老年代提前进行GC,预留足够的空间来接纳新生代的晋升对象。

 

实战中,需要结合真实情况,对症下药~

 

你可能感兴趣的:(JVM)