一次 Young GC 的优化实践(FinalReference 相关)
本文转载自公众号:涤生的博客,阅读时间大约需要11分钟。涤生的文章看起来跟破案一样,很精彩,很有启发。
前言
博客已经好久没有更新了,主要原因是 18 年下半年工作比较忙,另外也没有比较有意思的题材,所以迟迟没有更新。此篇是 18 年底的微信上的某同学提供的一个 Young GC 问题案例,找我帮忙解决。这个 GC 案例比较有意思,虽然过去有一段时间了,但是想想觉得还是有必要写出来,应该对大家很有帮助。排查问题有点像侦探断案,先分析各种可能性,再按照获得的一个个证据,去排除各种可能性、然后定位原因,最终解决问题。
问题
某同学在微信上问我,有没有办法排查 YoungGC 效率低的问题?听到这话,我也是不知从何说起,就让他说下具体情况。具体情况是:有个服务在没有 RPC 调用时,YoungGC 时间大约在 4-5ms,但是有 RPC 调用时,YoungGC 的耗时在 40ms 以上,几乎没有什么对象晋升,频率 4-5 秒一次。GC 日志截图如下。
后来他为了排查问题,把服务只留一个 RPC 调用,结果 YoungGC 更严重,变成 100ms 以上,几乎没有什么对象晋升,另外 RPC 调用耗时在 4-5ms,压测的 QPS 也比较低,只有几个线程在压。GC 日志截图如下。
另外还有一个奇葩的现象,如果测试时,只留一个调用耗时更长的 RPC 进行测试,发现 Young GC 耗时会小一点。这里也提供下提供了下 GC 参数如下:
//GC 参数
-Xmn700m -Xms3072m -Xmx3072m -XX:SurvivorRatio=8
-XX:MetaspaceSize=384m -XX:MaxMetaspaceSize=384m -XX:+UseConcMarkSweepGC
-XX:+CMSScavengeBeforeRemark -XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGC -XX:+PrintGCDateStamps
-XX:+PrintGCDetails
可以看到,整个堆 3072M,Young Gen只有 700M,都不大。
疑惑
从上述问题来看可以判断出:RPC 调用影响了 YoungGC 的时间。但是你一定有很多疑惑:
为什么进行 RPC 调用和不进行 RPC 调用相比 YoungGC 耗时增加那么多?(Young Gen 的空间一直那么大,而且每次 GC 几乎没有对象晋升到 Old Gen,)
为什么 RPC 调用耗时长短也会影响 YoungGC 的耗时?
分析
首先,大家都知道 Young GC 是全程 stop the world 的,时间可能有多方面原因决定:
各个线程到达安全点的等待时间;
从 GC Root 扫描对象,进行标记的时间;
存活对象 copy 到 Survivor 以及晋升 Old Gen 到的时间;
GC 日志的时间。
原因比较多,从表象上很难看出 YoungGC 耗时的原因,因此,我们需要收集更多的证据,来排除干扰选项,定位原因
对于是否线程到达安全点时间引起的原因, 我们加上显示 Stop 时间与 Safepoint 的相关参数
//Stop时间与Safepoint的相关参数
-XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
结论也很明显,stopping threads took 的时间都很短,可以排除此项因素。
对于从 GC Root 扫描对象,进行标记的时间引起的原因 我们加上显示 GC 处理 Reference 耗时的相关参数
// 打印参数
-XX:+PrintReferenceGC
结论也很明显,YoungGC 总耗时 110ms, 而 reference 处理耗时较长,主要是 FinalReference,耗时有 86 ms。
//YoungGC 日志
2019-01-02T17:42:53.926+0800: 409.638: [GC (Allocation Failure)
2019-01-02T17:42:53.927+0800: 409.638: [ParNew2019-01-02T17:42:53.950+0800: 409.662: [SoftReference, 0 refs, 0.0000893 secs]
2019-01-02T17:42:53.951+0800: 409.662: [WeakReference, 185 refs, 0.0000499 secs]
2019-01-02T17:42:53.951+0800: 409.662: [FinalReference, 38820 refs, 0.0865010 secs]
2019-01-02T17:42:54.037+0800: 409.749: [PhantomReference, 0 refs, 1 refs, 0.0000447 secs]
2019-01-02T17:42:54.037+0800: 409.749: [JNI Weak Reference, 0.0000220 secs]: 645120K->37540K(645120K), 0.1126527 secs]
1005305K->397726K(3074048K), 0.1128549 secs]
[Times: user=0.40 sys=0.00, real=0.11 secs]
对于存活对象 Copy 到 Survivor 以及晋升 Old Gen 到的时间引起的原因 由于 Survivor 较小,每次 YoungGC 又几乎没有晋升到 Old Gen 的对象,因此很明显,可以排除此项因素。
对 GC 日志的时间;大部分 GC 日志是不耗时的,除非机器使用了大量的 swap 空间,或者其他原因导致的 iowait 较高,此项可以通过 top 或者 dstat 等命令看看 swap 使用情况以及 iowait 指标。
分析到这里,其实问题基本已经定位了,主要是 FinalReference 的处理时间比较长,导致 Young GC 时间比较长。
原理
FinalReference 是什么?
FinalReference 的具体细节,又需要一篇文章来讲解。这里简单描述下:对于重载了 Object 类的 finalize 方法的类实例化的对象(这里称为 f 对象),JVM 为了能在 GC 对象时触发 f 对象的 finalize 方法的调用,将每个 f 对象包装生成一个对应的 FinalReference 对象,方便 GC 时进行处理。
//finalize方法
protected void finalize() throws Throwable { ....
}
FinalReference 详细解读,可以看下你假笨大神的这篇博客JVM源码分析之FinalReference完全解读
FinalReference 来源何处?
FinalReference 对于没有实现 finalize 的程序,一般是不会出现的,到底是来源何处呢?这里进行 JVM dump,然后通过 MAT 工具分析
很明显,是 SocksSocketImpl 对象,我们看下 SocksSocketImpl 类实现
//SocksSocketImpl finalize 的实现
/** * Cleans up if the user forgets to close it. */
protected void finalize() throws IOException { close();
}
这里是为了防止 Socket 连接忘记关闭导致资源泄漏而进行的保底措施。
为什么FinalReference GC 处理这么耗时?
为什么 JVM GC 处理 FinalReference 这么耗时呢,通过 GC 日志,可以看出有 38820 个 reference,耗时 86ms。
2019-01-02T17:42:53.951+0800: 409.662: [FinalReference, 38820 refs, 0.0865010 secs]
对于这个问题撸过 JVM 源码,但是一直没有搞清楚, 其实我的另一篇博客 PhantomReference导致CMS GC耗时严重,也是类似,reference 个数不多,但是 GC 处理非常耗时,影响系统性能。
如何解释问题的想象?
看到上面的 FinalReference 主要是 Socket 引起的,当时就推想到为什么会有这么多 Socket 对象需要 GC,所以问某同学难道你使用的是短连接?得到的回答是肯定的,瞬间豁然开朗。上文提到的两个疑惑就很容易解释了:
对于“为什么进行 RPC 调用和不进行 RPC 调用相比 YoungGC 耗时增加那么多?”问题 RPC 调用使用的是短连接,每调用一次就会创建一个 Socket 对象,致使 FinalReference 对象非常多, 因此,YoungGC 耗时增加。
对于“为什么 RPC 调用耗时长短也会影响 YoungGC 的耗时?”问题 由于 RPC 调用耗时长的,同样的线程数,调用的 QPS 就低,QPS 低自然创建的 Socket 对象就少,致使 FinalReference 对象少,因此,YoungGC 耗时相比就会小一些。
解决
理解了问题产生的原理,解决问题自然变得非常简单。
通用方法
加上 ParallelRefProcEnabled 参数可以使得 Reference 在 GC 的时候多线程并行处理过程,自然耗时就会降下来。
//ParallelRefProcEnabled 参数
-XX:+ParallelRefProcEnabled
减少 GC 的 Reference 数量
减少 GC 的 Reference 方法比较多,不同的案例不同的处理方法,能减少 GC 的 Reference 数量就好。这里也很简单,RPC 调用短连接改用长链接,自然就能减少 GC 的 Reference 数量。该案例就使用了这个方案,效果也很明显,YoungGC 时间直接降低到了 14ms。
总结
本案例总结原因就是 RPC 使用短连接调用,导致 Socket 的 FinalReference 引用较多,致使 YoungGC 耗时较长。因此,通过将短连接改成长连接,减少了 Socket 对象的创建,从而减少 FinalReference,来降低 YoungGC 耗时。在看本篇文章之前,你一定不会想到 JVM GC 处理 FinalReference 耗时这么长;你也一定不会想到短连接还有影响 GC 耗时的坏处。排查问题的过程,很享受,不仅可以证明所学,也可以锤炼技术。
下方查看历史文章
MAT入门到精通(一)
MAT入门到精通(二)
99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?
016:字符串对象在JVM中是如何存放的
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
一次 Young GC 的优化实践(FinalReference 相关)相关推荐
- 一次 Young GC 的优化实践
这个 GC 案例比较有意思,排查问题有点像侦探断案,先分析各种可能性,再按照获得的一个个证据,去排除各种可能性.然后定位原因,最终解决问题. 问题 某同学在微信上问我,有没有办法排查 YoungGC ...
- 案例:来自Uber的Go语言GC优化实践
Uber 是世界领先的生活出行服务提供商,也是 Go 语言的早期 adopter,根据 Uber 工程博客的内容,大致可以判断出 Go 语言在 Uber 内部扮演了十分重要的角色.Uber 内部的 G ...
- 如何降低90%Java垃圾回收时间?以阿里HBase的GC优化实践为例
阿里妹导读:GC一直是Java应用中讨论的一个热门话题,尤其在像HBase这样的大型在线存储系统中,大堆下(百GB)的GC停顿延迟产生的在线实时影响,成为内核和应用开发者的一大痛点. 过去的一年里,我 ...
- 高吞吐、低延迟 Java 应用的 GC 优化实践
2019独角兽企业重金招聘Python工程师标准>>> 背景 高性能应用构成了现代网络的支柱.LinkedIn 内部有许多高吞吐量服务来满足每秒成千上万的用户请求.为了获得最佳的用户 ...
- 阿里HBase的GC优化实践,能够降低java垃圾回收时间!
GC一直是Java应用中讨论的一个热门话题,尤其在像HBase这样的大型在线存储系统中,大堆下(百GB)的GC停顿延迟产生的在线实时影响,成为内核和应用开发者的一大痛点. 过去的一年里,我们准备在Al ...
- young GC和Full GC的区别、什么时候触发young gc和Full GC、如何优化GC
young GC 和 Full GC 的含义及区别? young GC(新生代GC):指发生在新生代的垃圾收集动作,新生代中的对象朝生夕死,所以 Minor GC 非常频繁,回收速度也比较快. Ful ...
- 一种KV存储的GC优化实践
作者:vivo 互联网服务器团队- Yuan Jian Wei 从内部需求出发,我们基于TiKV设计了一款兼容Redis的KV存储.基于TiKV的数据存储机制,对于窗口数据的处理以及过期数据的GC问题 ...
- ELK 性能优化实践
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 一.背景介绍 近一年内对公司的 ELK 日志系统做过性能优 ...
- 陆金所 CAT 优化实践
1 背景 CAT 介绍 CAT (Central Application Tracking)是一个实时监控系统,由美团点评开发并开源,定位于后端应用监控.应用集成客户端的方式上报中间件和业务数据,支持 ...
最新文章
- python之np.var()函数解析
- php数组分开_PHP学习之五:数组(三)合并、拆分、接合和分解数组
- WINKEY功能键你会用吗??
- 版本变迁_冰枪?卢登?大天使?——从发条魔灵的装备变迁看版本变动
- 『转载』hadoop2.x常用端口、定义方法及默认端口
- 使用jps来查看虚拟机状态
- Bootstrap 中的工具Mixin
- Java中调用FTP服务时inputStream获取一直为null
- linux下使用iptables NAT上网
- css中flex设置为1是什么意思
- 裸辞两个月,海投一个月,从 Android 转战 Web 前端的求职之路
- spring成神之路第十八篇:@ComponentScan、@ComponentScans 详解(bean 批量注册)
- 《大话移动通信(第2版)》赠书活动名单公告
- qt 获取当前程序运行路径_Qt 程序获取程序所在路径、用户目录路径、临时文件夹等特殊路径的方法...
- 大型系统存储层迁移实践
- QT实现ping功能
- 数据集:人群行为识别数据库总结
- 八位二进制数能表示数的范围以及原码、反码和补码含义
- WebView的简单介绍和Android使用WebView无法显示网页
- php 银行支付通道_接口--php对接农行网上支付平台-b2b
热门文章
- Linux系统的基本使用指南(速成,帮助快速上手使用Linux系统)
- Pr:导出设置之编码设置
- Python最全学习路线
- 解决win7 anaconda 安装 Failed to create menus
- Hadoop 3.x|第二天|配置完全分布式运行模式(重点)
- html把图像放在指定位置,如何在HTML中将一个图像放在另一个图像的顶部?
- 又订阅了万维刚的精英日课
- 总结在mac电脑上安装brew
- 【推荐】2020,2021网易数字+大会(云原生微服务+大数据数据库+网易AI实践集合+其他) - (共187份)
- 关于H5中的Canvas API的探索