点击上方 好好学java ,选择 星标 公众号

重磅资讯、干货,第一时间送达

今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招!

 个人原创100W+访问量博客:点击前往,查看更多

1. 背景

多个业务线的应用出现LongGC告警

最近一段时间,经常收到CAT报出来的Long GC告警(配置为大于3秒的为Longgc)。

2. 知识回顾

2.1 JVM堆内存划分

  • 新生代(Young Generation)

新生代内被划分为三个区:Eden,from survivor,to survivor。大多数对象在新生代被创建。Minor GC针对的是新生代的垃圾回收。

  • 老年代(Old Generation)

在新生代中经历了几次Minor GC仍然存活的对象,就会被放到老年代。Major GC针对的是老年代的垃圾回收。本文重点分析的CMS就是一种针对老年代的垃圾回收算法。另外Full GC是针对整堆(包括新生代和老年代)做垃圾回收的。

  • 永久代(Perm)

主要存放已被虚拟机加载的类信息,常量,静态变量等数据。该区域对垃圾回收的影响不大,本文不会过多涉及。

2.2 CMS垃圾回收的6个重要阶段

1、initial-mark 初始标记(CMS的第一个STW阶段),标记GC Root直接引用的对象,GC Root直接引用的对象不多,所以很快。

2、concurrent-mark 并发标记阶段,由第一阶段标记过的对象出发,所有可达的对象都在本阶段标记。

3、concurrent-preclean 并发预清理阶段,也是一个并发执行的阶段。在本阶段,会查找前一阶段执行过程中,从新生代晋升或新分配或被更新的对象。通过并发地重新扫描这些对象,预清理阶段可以减少下一个stop-the-world 重新标记阶段的工作量。

4、concurrent-abortable-preclean 并发可中止的预清理阶段。这个阶段其实跟上一个阶段做的东西一样,也是为了减少下一个STW重新标记阶段的工作量。增加这一阶段是为了让我们可以控制这个阶段的结束时机,比如扫描多长时间(默认5秒)或者Eden区使用占比达到期望比例(默认50%)就结束本阶段。

5、remark 重标记阶段(CMS的第二个STW阶段),暂停所有用户线程,从GC Root开始重新扫描整堆,标记存活的对象。需要注意的是,虽然CMS只回收老年代的垃圾对象,但是这个阶段依然需要扫描新生代,因为很多GC Root都在新生代,而这些GC Root指向的对象又在老年代,这称为“跨代引用”。

6、concurrent-sweep ,并发清理。

3. 分析

下面先看看出现LongGC时发生了什么。

选取其中一个应用分析其GC日志,发现LongGC发生在CMS 的收集阶段。

箭头1 显示abortable-preclean阶段耗时4.04秒。箭头2 显示的是remark阶段,耗时0.11秒。

虽然abortable-preclean阶段是concurrent的,不会暂停其他的用户线程。就算不优化,可能影响也不大。但是天天收到各个业务线的gc报警,长久来说也不是好事。

在调优之前先看下该应用的GC统计数据,包括GC次数,耗时:

统计期间内(18天)发生CMS GC 69次,其中 abortable preclean阶段平均耗时2.45秒,final remark阶段平均112ms,最大耗时170ms.

4. 优化目标

降低abortable preclean 时间,而且不增加final remark的时间(因为remark是STW的)。

5. JVM参数调优

5.1 第一次调优

先尝试调低abortable preclean阶段的时间,看看效果。

有两个参数可以控制这个阶段何时结束:

  • -XX:CMSMaxAbortablePrecleanTime=5000

默认值5s,代表该阶段最大的持续时间

  • -XX:CMSScheduleRemarkEdenPenetration=50

默认值50%,代表Eden区使用比例超过50%就结束该阶段进入remark

调整为最大持续时间为1s,Eden区使用占比10%,如下:

-XX:CMSMaxAbortablePrecleanTime=1000

-XX:CMSScheduleRemarkEdenPenetration=10

为什么调整成这样两个值,我们是这样考虑的:首先每次CMS都发生在老年代使用占比达到80%时,因为这是由下面两个参数决定的:

-XX:CMSInitiatingOccupancyFraction=80

-XX:+UseCMSInitiatingOccupancyOnly

而老年代的增长是由于部分对象在Minor GC后仍然存活,被晋升到老年代,导致老年代使用占比增长的,也就是在每次CMS GC发生之前刚刚发生过一次Minor GC,所以在那一刻新生代的使用占比是很低的。那么我们预计这个时候尽快结束abortable preclean阶段,在remark时就不需要扫描太多的Eden区对象,remark STW的时间也就不会太长。

调整的思路是这样了,那到底效果如何呢?

第一次调整的的结果

在统计期间(17小时左右)内,发生过2次CMS GC。Abortable Preclean 平均耗时835ms,这是预期内的。但是Final Remark 平均耗时495ms(调整前是112ms),其中一次是80ms,另一次是910ms!将近1秒钟!Remark是STW的!对于要求低延时的应用来说这是无法接受的!

对比这两次CMS GC的详细GC日志,我们发现了一些对分析问题非常有用的东西。

remark耗时80ms的那次GC日志

[YG occupancy: 181274 K (1887488 K)] - 年轻代当前占用情况和总容量

耗时80ms的这次remark发生时(早上9点,非高峰时段),新生代(YG)占用181.274M。

remark耗时910ms的那次GC日志

[YG occupancy: 773427 K (1887488 K)]

耗时910ms的这次remark发生时(晚上10点左右,高峰时段),新生代(YG)占用773.427M。因为这个时候高峰期,新生代的占用量上升的非常快,几乎同样的时间内,非高峰时段仅上升到181M,但是高峰时段就上升到773M。

这里能得出一个有用的结论:如果abortale preclean阶段时间太短,随后在remark时,新生代占用越大,则remark持续的时间(STW)越长。

这就陷入了两难了,不缩短abortale preclean耗时会报longgc;缩短的话,remark阶段又会变长,而且是STW,更不能接受。

对于这种情况,CMS提供了CMSScavengeBeforeRemark参数,尝试在remark阶段之前进行一次Minor GC,以降低新生代的占用。

-XX:+CMSScavengeBeforeRemark

Enables scavenging attempts before the CMS remark step. By default, this option is disabled.

5.2 第二次调优

调优前的考虑:

增加-XX:+CMSScavengeBeforeRemark 不是没有代价的,因为这会增加一次Minor GC停顿。所以这个方案好或者不好的判断标准就是:增加CMSScavengeBeforeRemark参数之后的minor GC停顿时间 + remark 停顿时间如果比增加之前的remark GC停顿时间要小,这才是好的方案。

第二次调整的结果

在统计期间(20小时左右)内,发生3次CMS GC。Abortable preclean 平均耗时693ms。Final remark平均耗时50ms,最大耗时60ms。Final remark的时间比调优前的平均时间(112ms)更低。

那么CMS GC前的Minor GC停顿时间又如何呢?来看看详细的GC日志。

3次CMS GC remark前的Minor GC日志分析

第1次是非高峰时段的表现,Minor GC 耗时 0.01s + remark耗时 0.06s = 0.07s = 70ms,如下

第2次是高峰时段,Minor GC 耗时 0.01s + remark耗时 0.05s = 0.06s = 60ms,如下

第3次是非高峰时段,Minor GC 耗时 0.00s + remark耗时 0.04s = 0.04s = 40ms,如下

所以,3次Minor GC + remark耗时的平均耗时 < 60ms,这比第一次调优时remark平均耗时495ms好得多了。

6.优化结果

至此,我们最初的目标- 降低abortable preclean 时间,而且不增加final remark的时间 ,已经达到了。甚至remark的时间也缩短了。

7. 小结

解决abortable preclean 时间过长的方案可以归结为两步:

  • 缩短abortable preclean 时长,通过调整这两个参数:

-XX:CMSMaxAbortablePrecleanTime=xxx

-XX:CMSScheduleRemarkEdenPenetration=xxx

调整为多少的一个判断标准是:abortable preclean阶段结束时,新生代的空间占用不能大于某个参考值。 在前面第一次调优后,新生代(YG)占用181.274M,remark耗时80ms;新生代(YG)占用773.427M时,remark耗时910ms。所以这个参考值可以是300M。而如果新生代增长过快,像这次调优应用2秒内就能用光2G新生代堆空间的,就只能通过CMSScavengeBeforeRemark做一次Minor GC了。

  • 增加CMSScavengeBeforeRemark参数开启remark前进行Minor GC的尝试

虽然官方说明这个增加这个参数是尝试进行Minor GC,不一定会进行。但实际使用起来,几乎每次remark前都会Minor GC。

8. 总结

  1. 调优前明确目标

  2. 调优过程对GC指标进行数据统计分析(本文借助gceasy.io在线分析工具)来验证效果

  3. 需要能看懂GC日志

  4. GC调优不是一个一蹴而就的事情,它是微调-观察-再微调的过程。所以需要比较深入了解GC的一些基础,才能少走弯路。

推荐文章

  • 硬刚一周,3W字总结,一年的经验告诉你如何准备校招!

  • 今年的校招,Java 好拿 offer 吗?

  • 10月了,该聊聊今年秋招了!

  • 聊聊在腾讯实习快一个月的感受

原创电子书历时整整一年总结的 Java 面试 + Java 后端技术学习指南,这是本人这几年及校招的总结,各种高频面试题已经全部进行总结,按照章节复习即可,已经拿到了大厂offer。
原创思维导图扫码或者微信搜 程序员的技术圈子 回复 面试 领取原创电子书和思维导图。

JVM GC耗时频频升高,这次排查完想说:还有谁?相关推荐

  1. 教你九种 JVM GC 问题的排查方法

    目前,互联网上 Java 的 GC 资料要么是主要讲解理论,要么就是针对单一场景的 GC 问题进行了剖析,对整个体系总结的资料少之又少.前车之鉴,后事之师,美团的几位工程师历时一年多的时间,搜集了内部 ...

  2. 30.jvm.gc(GC之详解CMS收集过程和日志分析)

    30.jvm.gc(GC之详解CMS收集过程和日志分析) 30.1.话题引入 30.2.ParNew and CMS 30.3.日志 30.3.1.GC日志初体验 30.3.2.Minor GC 30 ...

  3. 一文看尽 JVM GC 调优

    一个著名的学习方法论 向橡皮鸭求助 学会提问,提问也是一门艺术 提问前,先投入自己的时间做好功课 发生了什么事情 问题的基本情况 你投入的研究和发现 能正确提出你的问题,你的问题差不多已经解决一半 深 ...

  4. 【JVM · GC】垃圾回收器

    1. GC分类与性能指标 1.1 垃圾回收期器概述 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商.不同版本的JVM来实现. 由于 JDK 版本处于高速迭代过程中,因此Java 发展至今已经衍 ...

  5. JVM GC 日志详解

    本文采用的JDK版本: java version "1.8.0_144" Java(TM) SE Runtime Environment (build 1.8.0_144-b01) ...

  6. Kafka如何在千万级别时优化JVM GC问题?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://www.toutiao.com ...

  7. 面试官问:上亿数据量下,Kafka是如何优化JVM GC问题的?

    大家都知道Kafka是一个高吞吐的消息队列,是大数据场景首选的消息队列,这种场景就意味着发送单位时间消息的量会特别的大,那既然如此巨大的数据量,kafka是如何支撑起如此庞大的数据量的分发的呢? 今天 ...

  8. 统计各个函数的耗时_分享一次CMS GC耗时狠高优化过程全记录

    1. 背景 多个业务线的应用出现LongGC告警 最近一段时间,经常收到CAT报出来的Long GC告警(配置为大于3秒的为Longgc). 2. 知识回顾 2.1 JVM堆内存划分 新生代(Youn ...

  9. java虚拟机jvm GC介绍

    在虚拟机中,释放那些不再被使用的对象所占用空间的的过程称作垃圾回收,简称GC. 垃圾回收的三个难题: 如何确定哪些对象不再被使用? 什么时候进行回收? 如何回收? 如何确定对象无用 引用计数算法 引用 ...

最新文章

  1. Android 百度鹰眼轨迹SDK(v2.1.6)
  2. 常见linux网络参数
  3. SpringBoot-@PathVariable
  4. winform技巧一,errorprovider,任务栏可见,总在最前
  5. python将ros下bag文件的所有topic解析为csv格式
  6. 防抓包重放php,超简单最基本的WEB抓包改包重放的方法
  7. 网络协议osi模型_网络协议|OSI模型第二层数据链路层
  8. Leetcode 363.矩形区域不超过k的最大数值和
  9. 木蚂蚁软件光盘 V2.0 2008元旦贺岁版
  10. 无线通信与编码_MATLAB仿真实现Jakes信道模型_含仿真代码_瑞利衰落信道模型
  11. 2021年中国柠檬酸供需现状与行业前景分析,受出口景气度上升价格持续上涨「图」
  12. 大数据仓库之拉链表讲解与举例说明【基础部分】
  13. HTML5游子吟网页的完整代码,《游子吟》教学设计(5页)-原创力文档
  14. nod32半年升级id
  15. blowfish java_Java与C++通过CBC、blowfish互相加解密
  16. ultravnc 反向连接_C程序以反向显示链接列表
  17. 在安装SVN时出现Custom action GenerateSSLKey failed: Command terminated with non-zero exit code
  18. 秋招春招,1v1面试技巧和常见问题
  19. java 两个list合并
  20. 0017---正方体的表面积和体积

热门文章

  1. php的yii框架开发总结2
  2. springMVC笔记day01
  3. 有关表格边框的css样式表语法说明
  4. ABAP:FTP Using SAP Functions
  5. Linux 服务器带宽异常跑满分析解决
  6. 波卡链Substrate (4)托盘Pallets
  7. 区块链BaaS云服务(29) 溪塔科技 CITA-Cloud
  8. 以太坊知识教程------交易
  9. C++ Primer 5th笔记(8)chapter8 类:IO库-文件流
  10. [HOW TO]-ubuntu20.10搭建openjrok服务指南