一次 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 耗时的坏处。排查问题的过程,很享受,不仅可以证明所学,也可以锤炼技术。
下方查看历史文章
99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
一次 Young GC 的优化实践(FinalReference 相关)的更多相关文章
- Unity3D游戏GC优化总结---protobuf-net无GC版本优化实践
protobuf-net优化效果图 protobuf-net是Unity3D游戏开发中被广泛使用的Google Protocol Buffer库的c#版本,之所以c#版本被广泛使用,是因为c++版本的 ...
- 一次young gc耗时过长优化过程
1 问题源起 上游系统通过公司rpc框架调用我们系统接口超时(默认超时时间为100ms)数量从50次/分突然上涨到2000次/分,在发生变化时间段里我们的系统也没有做过代码变更,但上游系统的调用 ...
- 如何降低90%Java垃圾回收时间?以阿里HBase的GC优化实践为例
过去的一年里,我们准备在Ali-HBase上突破这个被普遍认知的痛点,为此进行了深度分析及全面创新的工作,获得了一些比较好的效果.以蚂蚁风控场景为例,HBase的线上young GC时间从120ms减 ...
- 高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和 ...
- ELK 性能优化实践
文章转载自:https://mp.weixin.qq.com/s?__biz=MzI5MTU1MzM3MQ==&mid=2247489814&idx=1&sn=6916f8b7 ...
- Redis各种数据结构性能数据对比和性能优化实践
很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...
- Java GC性能优化实战
GC优化是必要的吗? 或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以 ...
- 爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结
本文由爱奇艺技术团队原创分享,原题<爱奇艺Android客户端启动优化与分析>. 1.引言 互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Andro ...
- 【实战分享】又拍云 OpenResty / Nginx 服务优化实践
2018 年 11 月 17 日,由 OpenResty 主办的 OpenResty Con 2018 在杭州举行.本次 OpenResty Con 的主题涉及 OpenResty 的新开源特性.业界 ...
随机推荐
- 简述 gevent模块的作用和应用场景。
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成, 再在适当的时候切换回来继续执行.由于IO操作非常耗时,经常使程序处于等待状态, 有了geve ...
- vue 工具函数的封装 时间格式化函数
时间代码格式化工具函数的封装 小伙伴们,多封点工具函数,多封装点公共组件,多写点公共样式,照顾下互联网行业的新人把....~~~~~ /** yyyymmdd(new Date) -> &quo ...
- CF Round #600 (Div 2) 解题报告(A~E)
CF Round #600 (Div 2) 解题报告(A~E) A:Single Push 采用差分的思想,让\(b-a=c\),然后观察\(c\)序列是不是一个满足要求的序列 #include< ...
- (转)Qt中文手册 之 QApplication
QApplication管理GUI程序的控制流和主要设置. QApplication包含由窗口系统和其他来源处理过和发送过的主事件循环.它也处理应用程序的初始化和收尾工作,并提供对话管理.QAppli ...
- 洛谷 P1807 最长路_NOI导刊2010提高(07)题解
相当与一个拓扑排序的模板题吧 蒟蒻的辛酸史 题目大意:给你一个有向无环图,让你求出1到n的最长路,如果没有路径,就输出-1 思路:一开始以为是一个很裸的拓扑排序 就不看题目,直接打了一遍拓扑排序 然后 ...
- 接口自动化框架2-升级版(Pytest+request+Allure)
前言: 接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱. 第一版入口:接口自动化框架(Pytest+request+Allure) 本次版本做了一些升级 ...
- redis 键值对 有效期设置
redis 键值对 有效期设置redis中可以使用expire命令设置一个键的生存时间, 到时间后redis会自动删除它<-----> 类比于javaweb系统临时数据 过期删除功能 ex ...
- ping-pong buffer
1 什么是pingpong? pingpong是一种数据缓存的手段,通过pingpong操作可以提高数据传输的效率. 2 什么时候需要pingpong? 在两个模块间交换数据时,上一级处理的结果不能马 ...
- Spring Security教程(二)
上一篇博客中,Spring Security教程(一),我把用户信息和权限信息放到了xml文件中,这是为了演示如何使用最小的配置就可以使用Spring Security,而实际开发中,用户信息和权限信 ...
- 【Activiti学习之八】Spring整合Activiti
环境 JDK 1.8 MySQL 5.6 Tomcat 8 idea activiti 5.22 activiti-explorer是官方提供的一个演示项目,可以使用页面管理Activiti流程.ac ...