JVM调优实战:G1中的to-space exhausted问题

点击蓝色“程序猿DD”关注我

回复“资源”获取独家整理的学习资料!

作者:阿杜

来源:公众号「javaadu」

>> 「开学季」当当大促!4-5折优惠不了解一下? <<

最近刚刚将自己的一个应用从CMS升级到G1,在一天早上,刚刚到办公室坐下,就收到手机一阵报警,去查看了监控,发现机器的内存出现了一个90度的涨幅,如下图所示:

JVM调优实战:G1中的to-space exhausted问题_第1张图片

在查看GC日志后,发现那个时间点附近出现了“to-space exhausted”这种日志(关于G1的日志的学习,可以参考我之前的文章:【译】深入理解G1的GC日志(一))

JVM调优实战:G1中的to-space exhausted问题_第2张图片

在这里我比较奇怪的是为啥to-sapce exhausted会导致整个机器的内存激增。我们JVM团队同学给我的解释是:老区不够了,这个时候会把young区所有对象不管死活都转成old区对象,所以总的内存使用量会暴增。这一个知识点我之前学习G1的时候还真没有get到(关于G1的总结,参见我之前的文章:可能是最全面的G1学习笔记)。

不过我有另外一个疑问:xmx和xms相同的话堆空间应该不变,一开始就分配5g,然后加上非堆内存,那么java进程起来后就会超过5g,这是没问题的;但是这里利用空闲的内存也应该是利用堆上的空间,然后整体的内存块应该已经分配出去了,应该不会出现机器内存激增的情况。JVM团队的同学给我解释道:没有,第一次读写到了才会实际从os分配出来物理内存

针对上面的问题,我们最终确定了下面的调优建议:

  • 这次没有发生FGC,可能是由于我前面将xmx和xms调大了导致的,这次准备将xmx和xms先调回到原来的值;

  • 加上HeapDumpAfterFullGC参数,下次再发生类似情况的时候,就会触发FGC,然后自动dump堆内存,就可以针对堆内存进行分析,看看是什么对象占用了这么多内存,然后就可以针对性优化。

基于上面这个问题,我又去找了一些资料,整理如下。

《Java性能权威指南》

在这本书的123页有提到,上面这种情况属于晋升失败的情况——G1收集器完成了标记阶段,开始启动混合式垃圾收集,准备要清理老年代分区,但是老年代分区在垃圾收集器释放出足够的空间之前就已经被耗尽了。这种失败通常意味着混合式垃圾收集需要更迅速得完成垃圾收集,每次新生代垃圾收集需要处理更多的老年代分区。一般来说,一系列的to-space exhausted之后会跟着一次FGC。

在我们上面的这个例子中,是old区的使用速度超过了垃圾收集器的回收速度,因此可以考虑两种调优的思路

  • 让G1更早得启动混合式垃圾收集周期,通过调小-XX:InitiatingHeapOccupancyPercent=N这个参数,默认情况下该参数是45(PS:这个参数表示的是占用整个堆内存的比例),不过,这个参数也不能调得太小,否则会导致过多的并发收集周期和混合式垃圾收集,给应用早成过多的停顿。

  • 除了考虑增加速度,还可以考虑增加每次混合式垃圾收集收集的Old分区数量,通过调整-XX:G1MixedGCCountTarget=N参数可以控制每个混合式周期中回收的Old分区数量,该参数的默认值是8;

《Java性能调优指南》

要特别关注日志片段中的"to-space exhausted"和“Evacuation Failure”两个日志,如下图所示。可以看出,Evacuation Failure消耗了684.1ms,也就是说,这次转移失败导致了将近1s的应用暂停。

JVM调优实战:G1中的to-space exhausted问题_第3张图片

这种情况属于转移失败,这本书给出了两点建议:

  • 和《Java性能权威指南》一样,也建议调小-XX:InitiatingHeapOccupancyPercent=N这个参数,因为转移失败的代价比多执行一些并发标记周期高很多;

  • 建议通过调整-XX:ConcGCThreads,增加用于垃圾收集的线程个数,代价是会多一些CPU的消耗;也就是会占用Java应用的CPU时间,这一点也需要权衡一下;

  • 有时候转移失败是由于survivor分区中没有足够的空间容纳新晋升的对象,如果是这种情况,还可以考虑增加-XX:G1ReservePercent的大小,在G1中这个默认值是10%,在一般情况下这10%的空间是浪费掉的,因此增大这个参数之前,也是要做个权衡;

总结

JVM参数的调优,是一个不断推导和尝试的过程,其中最重要的数据就是GC日志和Java堆内存快照,因此有两个基本点是一定要注意的:

  • 在JVM参数中一定要设置-XX:+HeapDumpAfterFullGC-XX:+HeapDumpOnOutOfMemoryError两个参数,可以在发送FGC和OOM的时候将当时的Java堆情况记录下来,用于事后分析;

  • GC日志要单独打印到一个日志文件中,方便分析,如果不特别设置则GC日志会打印到stdout.log中,和其他的日志混合在中间,影响后面的问题排查。

最后,JVM的参数调优并不是万能的,发生OOM或者FGC的时候,业务代码中也一定有不合理的地方,需要做合理的限制和优化,不能将所有的事情都交给JVM抗。

留言交流不过瘾?添加微信:zyc_enjoy

根据指引加入各种主题讨论群

每日一问

今日问题如下图所示,由10根火柴棒组成的椅子,只是这把椅子是倒着的,你能在只移动2根火柴棒的情况下,就把它正过来吗?来试一试吧。

JVM调优实战:G1中的to-space exhausted问题_第4张图片

(留言说说你的答案和解析吧,关注公众号,发送口令:Q20190827,核对正确答案)

昨日问答:点击>>查看<<

推荐阅读

  • 顶级项目管理工具 Top 10

  • 这几款好用超赞的 Google Chrome插件送给你!

  • IntelliJ IDEA 2019.2最新解读

  • Spring Cloud与Dubbo的完美融合之手

  • 狡猾的 AI 工程师,编个故事骗走 2 亿人民币...

签到计划

活动介绍:自律到极致-人生才精致:第13期

活动奖励:《Spring Cloud微服务:入门、实战与进阶》 x 10

扫描下方二维码,签到参与

JVM调优实战:G1中的to-space exhausted问题_第5张图片

推荐关注:阿杜的世界「javaadu」

JVM调优实战:G1中的to-space exhausted问题_第6张图片

点一点“阅读原文”小惊喜在等你

你可能感兴趣的:(JVM调优实战:G1中的to-space exhausted问题)