一次 Young GC 的优化实践

2023-11-16

这个 GC 案例比较有意思,排查问题有点像侦探断案,先分析各种可能性,再按照获得的一个个证据,去排除各种可能性、然后定位原因,最终解决问题。

问题

某同学在微信上问我,有没有办法排查 YoungGC 效率低的问题?听到这话,我也是不知从何说起,就让他说下具体情况。 具体情况是: 有个服务在没有 RPC 调用时,YoungGC 时间大约在 4-5ms,但是有 RPC 调用时,YoungGC 的耗时在 40ms 以上,几乎没有什么对象晋升,频率 4-5 秒一次。GC 日志截图如下。

后来他为了排查问题,把服务只留一个 RPC 调用,结果 YoungGC 更严重,变成 100ms 以上,几乎没有什么对象晋升,另外 RPC 调用耗时在 4-5ms,压测的 QPS 也比较低,只有几个线程在压。GC 日志截图如下。

另外还有一个奇葩的现象,如果测试时,只留一个调用耗时更长的 RPC 进行测试,发现 Young GC 耗时会小一点。 这里也提供下提供了下 GC 参数如下:

 
  1. //GC 参数

  2. -Xmn700m -Xms3072m -Xmx3072m -XX:SurvivorRatio=8

  3. -XX:MetaspaceSize=384m -XX:MaxMetaspaceSize=384m -XX:+UseConcMarkSweepGC

  4. -XX:+CMSScavengeBeforeRemark -XX:CMSInitiatingOccupancyFraction=80

  5. -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGC -XX:+PrintGCDateStamps

  6. -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 的相关参数

 
  1. //Stop时间与Safepoint的相关参数

  2. -XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1

结论也很明显,stopping threads took 的时间都很短,可以排除此项因素。

  • 对于从 GC Root 扫描对象,进行标记的时间引起的原因 我们加上显示 GC 处理 Reference 耗时的相关参数

 
  1. // 打印参数

  2. -XX:+PrintReferenceGC

结论也很明显,YoungGC 总耗时 110ms, 而 reference 处理耗时较长,主要是 FinalReference,耗时有 86 ms。

 
  1. //YoungGC 日志

  2. 2019-01-02T17:42:53.926+0800: 409.638: [GC (Allocation Failure)

  3. 2019-01-02T17:42:53.927+0800: 409.638: [ParNew2019-01-02T17:42:53.950+0800: 409.662: [SoftReference, 0 refs, 0.0000893 secs]

  4. 2019-01-02T17:42:53.951+0800: 409.662: [WeakReference, 185 refs, 0.0000499 secs]

  5. 2019-01-02T17:42:53.951+0800: 409.662: [FinalReference, 38820 refs, 0.0865010 secs]

  6. 2019-01-02T17:42:54.037+0800: 409.749: [PhantomReference, 0 refs, 1 refs, 0.0000447 secs]

  7. 2019-01-02T17:42:54.037+0800: 409.749: [JNI Weak Reference, 0.0000220 secs]: 645120K->37540K(645120K), 0.1126527 secs]

  8. 1005305K->397726K(3074048K), 0.1128549 secs]

  9. [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 时进行处理。

 
  1. //finalize方法

  2. protected void finalize() throws Throwable {

  3.    ....

  4. }

FinalReference 详细解读,可以看下你假笨大神的这篇博客JVM源码分析之FinalReference完全解读

FinalReference 来源何处?

FinalReference 对于没有实现 finalize 的程序,一般是不会出现的,到底是来源何处呢? 这里进行 JVM dump,然后通过 MAT 工具分析

很明显,是 SocksSocketImpl 对象,我们看下 SocksSocketImpl 类实现

 
  1. //SocksSocketImpl finalize 的实现

  2. /**

  3. * Cleans up if the user forgets to close it.

  4. */

  5. protected void finalize() throws IOException {

  6.      close();

  7. }

这里是为了防止 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 的时候多线程并行处理过程,自然耗时就会降下来。

 
  1. //ParallelRefProcEnabled 参数

  2. -XX:+ParallelRefProcEnabled

  3.  
  • 减少 GC 的 Reference 数量 

减少 GC 的 Reference 方法比较多,不同的案例不同的处理方法,能减少 GC 的 Reference 数量就好。 这里也很简单,RPC 调用短连接改用长链接,自然就能减少 GC 的 Reference 数量。 该案例就使用了这个方案,效果也很明显,YoungGC 时间直接降低到了 14ms。

总结

本案例总结原因就是 RPC 使用短连接调用,导致 Socket 的 FinalReference 引用较多,致使 YoungGC 耗时较长。因此,通过将短连接改成长连接,减少了 Socket 对象的创建,从而减少 FinalReference,来降低 YoungGC 耗时。 在看本篇文章之前,你一定不会想到 JVM GC 处理 FinalReference 耗时这么长;你也一定不会想到短连接还有影响 GC 耗时的坏处。 排查问题的过程,很享受,不仅可以证明所学,也可以锤炼技术。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一次 Young GC 的优化实践 的相关文章

  • Spring批处理返回自定义进程退出代码

    我有一个包含多个作业的 jar 我只想每次只执行一个作业并检索自定义退出代码 例如 我有基本工作 retrieveErrorsJob 配置 只需一步即可读取输入 XML 文件并将数据写入特定数据库表中 应用类 SpringBootAppli
  • 如何找出一个对象有多少个引用? [复制]

    这个问题在这里已经有答案了 是否可以和 或容易地找出任意对象有多少个传入引用 也就是说 有多少对象引用它 提前致谢 简短的回答是 你自己数一下 StackOverflow 的另一个问题有一些有用的答案和资源 是否可以获得对象引用计数 htt
  • JVM CPU 峰值故障排除

    我们在其中一台应用程序服务器上发现了一个有趣的 尽管相当严重 问题 在某个时间点 运行 Web 应用程序的 JVM 的 CPU 使用率开始上升 并持续上升 直到应用程序最终减慢到爬行 修复此问题的唯一方法是重新启动应用程序服务器软件 应用服
  • Java VM 突然退出且没有明显原因

    我的 Java 程序突然退出 没有抛出任何异常 也没有正常完成 这是一个问题 我正在写一个程序来解决欧拉计划 http projecteuler net s 这就是我得到的 private static final int INITIAL
  • 以编程方式设置最大 Java 堆大小

    有没有办法以编程方式设置最大 java 堆大小而不是作为 vm 参数 就像是 System getProperties put
  • 是否可以使 java.lang.invoke.MethodHandle 与直接调用一样快?

    我正在比较性能MethodHandle invoke以及直接静态方法调用 这是静态方法 public class IntSum public static int sum int a int b return a b 这是我的基准 Stat
  • getResourceAsStream(file) 在哪里搜索文件?

    我很困惑getResourceAsStream 我的包结构如下 src net floodlightcontroller invoked getResourceAsStream here resources floodlightdefaul
  • 显示JVM中当前运行的所有线程组和线程

    所以我的任务是显示所有线程组以及当前在 JVM 中运行的属于这些组的所有线程 输出时应首先显示线程组 然后在下面显示该组中的所有线程 这是针对所有线程组完成的 目前 我的代码将仅显示每个线程组 然后显示每个线程 但我不确定如何达到我所描述的
  • Scala 泛型 - 为什么我无法在泛型类中创建参数化对象?

    我目前正在学习scala 为什么此代码不起作用 class GenClass T var d T var elems List T Nil def dosom x T var y new T y 我得到 错误 需要类类型 但找到了 T 代替
  • Jprofile可以连接到docker中运行的JVM

    我是 JProfiler 的新手 我最近遇到了一个问题 我的Java应用程序在docker中运行 这意味着JVM在docker中运行 但我的jprofile安装在主机上 我知道 jprofiler 必须连接到 JVM 那么 jprofile
  • 估计 64 位 Java 中最大安全 JVM 堆大小

    在分析存在一些问题的 64 位 Java 应用程序的过程中 我注意到分析器本身 YourKit 正在使用真正大量的内存 我在 YourKit 启动脚本中得到的是 JAVA HEAP LIMIT Xmx3072m XX PermSize 25
  • 哪种语言(在 JVM 上运行)最适合创建 DSL?

    我们需要创建复杂的固定长度和可变长度字符串 这些字符串可能代表客户资料 订单等 你们建议使用哪种基于 JVM 的编程语言 想法是让最终用户使用此 DSL 创建字符串 所以我正在寻找验证 代码完成等 Groovy http docs code
  • 什么触发了java垃圾收集器

    我对 Java 中垃圾收集的工作原理有点困惑 我知道当不再有对某个对象的实时引用时 该对象就有资格进行垃圾回收 但是如果它有对实时对象的引用怎么办 可以说我有一个节点集合 它们再次引用更多节点 List 1 gt Node a gt Nod
  • Oracle 的商业 Hotspot JVM 相对于 OpenJDK 有哪些性能优势?

    正如这个问题中所描述的 OpenJDK 与 Java HotspotVM https stackoverflow com q 44335605 1593077 Oracle 的商业 Hotspot JVM 本质上是 OpenJDK 加上一些
  • java.library.path 中没有字体管理器

    以下代码在我的桌面上运行得很好 BufferedImage image new BufferedImage width height BufferedImage TYPE INT RGB Graphics g image getGraphi
  • JVM 是否会内联对象的实例变量和方法?

    假设我有一个非常紧密的内部循环 每次迭代都会访问和改变一个簿记对象 该对象存储有关算法的一些简单数据 并具有用于操作它的简单逻辑 簿记对象是私有的和最终的 并且它的所有方法都是私有的 最终的和 inline 下面是一个示例 Scala 语法
  • 为什么不在下一个 JVM 中删除类型擦除呢?

    Java 在 Java 5 中引入了泛型类型擦除 因此它们可以在旧版本的 Java 上运行 这是兼容性的权衡 我们已经失去了这种兼容性 1 https stackoverflow com questions 22610400 a progr
  • Scala REPL 中的递归重载语义 - JVM 语言

    使用 Scala 的命令行 REPL def foo x Int Unit def foo x String Unit println foo 2 gives error type mismatch found Int 2 required
  • Java:为什么它使用固定数量的内存?或者它如何管理内存?

    JVM 似乎使用了一些固定数量的内存 至少我经常看到参数 Xmx 对于最大尺寸 和 Xms 对于初始大小 这表明 我感觉 Java 应用程序不能很好地处理内存 我注意到一些事情 即使一些非常小的示例演示应用程序也会加载大量内存 也许这是因为
  • Java 语言中不可用的字节码功能

    当前 Java 6 是否有一些事情可以在 Java 字节码中完成而在 Java 语言中无法完成 我知道两者都是图灵完备的 所以将 可以做 理解为 可以做得更快 更好 或者只是以不同的方式 我正在考虑额外的字节码 例如invokedynami

随机推荐

  • 内存数据库SQLite和H2比较

    内存数据库 顾名思义就是将数据放在内存中直接操作的数据库 相对于磁盘 内存的数据读写速度要高出几个数量级 将数据保存在内存中相比从磁盘上访问能够极大地提高应用的性能 AD 2013云计算架构师峰会课程资料下载 本文中主要为大家介绍两种内存数
  • STM32学习——端口复用及映射

    1 复用 STM32有很多的内置外设 这些外设的外部引脚都是与GPIO复用的 也就是说 一个GPIO如果可以复用为内置外设的功能引脚 那么当这个GPIO作为内置外设使用的时候 就叫做复用 哪些端口可以复用为什么 这个查表就可以了 2 如何进
  • volatile 关键字 详解,为何不能保证复合操作的原子性

    一直对volatile 有些许的疑惑 就是它既然实时刷新主内存中的值 并且能保证可见 为啥不能保证原子性n 下面分析 使用volatile 关键字修饰共享变量时 变量就会有以下特点 1 变量对其他线程具有可见性 2 禁止进行指令重排 保证了
  • MATLAB如何画三轴图

    MATLAB如何画三轴图 前言 使用MATLAB绘图非常方便 它提供了非常丰富的图形 如 line bar stem等 用户可以直接调用相应的函数 但有时直接使用这些 高级 的函数不能满足我们的绘图要求 比如 如何绘制三Y轴的图形 即一个f
  • docker push 镜像上传至仓库

    目的 docker push chengzy busybox v2 问题 denied requested access to the resource is denied 原因 登录的账户名不匹配 解决 使用 tag 更改镜像名字前缀为
  • 数据库导入导出详解

    1 数据库导入导出 1 传统方式 exp 导出 和 imp 导入 2 数据泵方式 expdp 导出 和 impdp 导入 3 第三方工具 PL sql Developer 2 三种导入导出方式优缺点比较 2 1 exp imp 优点 代码书
  • deepin20.3 的问题

    deepin显示器无法唤醒解决方法 发现系统无法唤醒是因为和nvida驱动有冲突 当直接使用nvidia驱动的显卡作为显示器输入信号源就会出现这个问题 但如果小伙伴又需要使用NVIDIA的显卡运行深度学习程序 可以参考这个办法 安装deep
  • 解决win7下安装Mysql卡在Start service的问题

    由于之前在电脑上安装过MySQL 所以旧的服务器依然存在电脑上 再重新安装时startservice会报错 mysql下载地址http www mysql com downloads mysql 1 打开cmd 键入sc delete my
  • Linux日志误删了怎么办,Linux下误删messages文件的找回方法

    如果有进程正在使用的文件 如果被误删了 可以找回 如果没有进程在使用 就无法找回被误删的文件了 假如 var log messages文件被误删了 1 查询正在使用该文件的进程 root www lsof grep message rsys
  • 报错:selenium.common.exceptions.WebDriverException: Messag‘geckodriver‘ execute

    问题原因 使用pip安装selenium 默认安装的是最新版本的selenium selenium 3 x开始 webdriver firefox webdriver py的 init 中 executable path geckodriv
  • Git——Day3(Github Pages搭建个人网站)

    1 个人站点访问 https github用户名 github io 2 搭建步骤 1 创建个人站点 gt 新建仓库 注 仓库名必须是 用户名 github io 2 在仓库下新建index html的文件即可 注意 1 github pa
  • Python报错socket.gaierror: [Errno 11001] getaddrinfo failed

    1 报错 from scapy all import sr IP ICMP target 192 168 142 129 pkt IP dst target ICMP ans unans sr pkt timeout 1 for s r i
  • GitHub Desktop客户端下载安装,以及上传到服务端

    下载安装地址 https desktop github com 使用教程 https blog csdn net qqw666666 article details 125652869 操作流程 就是不同应用端的交互 做好相关验证即可
  • 应用中间件二、Tomcat单机多实例部署

    Tomcat 常见的几种部署场景 通常 我们在同一台服务器上对 Tomcat 部署需求可以分为以下几种 单实例单应用 单实例多应用 多实例单应用 多实例多应用 实例的概念可以理解为上面说的一个 Tomcat 目录 单实例单应用 比较常用的一
  • Python3.x opencv操作中文文件

    我用的是python3 5 本身用file打开中文文件是没有问题的 但是用opencv就不行 网上看到很多解决版本 可能都是针对python2 x的 没有效果 后来在知乎上看到一个解决方法 测试有效 引用在这里 冯卡门 由于python3字
  • Redis底层数据结构.md

    1 Redis 概述 Redis 数据库里面的每个键值对 key value 都是由对象 object 组成的 数据库键总是一个字符串对象 string object 数据库的值则可以是字符串对象 列表对象 list 哈希对象 hash 集
  • Jmeter对图片验证码的处理

    jmeter对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入 而且每次登录时图片验证码都是随机的 当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段 然后再登录接口中使用 通过jmeter对图片验证码的识
  • ctfshow—萌新—web1

    0x00 前言 CTF 加解密合集 CTF Web合集 0x01 题目 0x02 Write Up 解法1 标准的数字型注入 查列名 http cc3ecc3f 8c42 4624 979e 277a51ea85d2 challenge c
  • 【面经】外企德科-华为精英研发项目-笔试编程题

    微信搜索 编程笔记本 获取更多干货 微信搜索 编程笔记本 获取更多干货 点击上方蓝字关注我 我们一起学编程 欢迎小伙伴们分享 转载 私信 赞赏 今天来看一道 外企德科 华为精英研发项目 的一道笔试编程题 求满足条件的最长字串的长度 题目描述
  • 一次 Young GC 的优化实践

    这个 GC 案例比较有意思 排查问题有点像侦探断案 先分析各种可能性 再按照获得的一个个证据 去排除各种可能性 然后定位原因 最终解决问题 问题 某同学在微信上问我 有没有办法排查 YoungGC 效率低的问题 听到这话 我也是不知从何说起