产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

想知道垃圾回收暂停的过程中发生了什么吗?查查垃圾回收日志就知道了!

  • 2017-05-16
  • 本文字数:4439 字

    阅读完需:约 15 分钟

关键点

  • 垃圾回收日志中包括着一些关键性能指标;
  • 要做一次正确的垃圾回收分析需要收集许多数据,所以好的工具是非常必要的;
  • 除了垃圾回收之外还有很多事件都可能会让应用程序暂停;
  • 让你的应用程序暂停的事件也会让垃圾回收器暂停;
  • 要发现到底是什么原因导致了性能上的回退,需要有正确的分析方法;

我最近收到了一份非常难得一见的垃圾回收日志,其中包含了一次长达整整 23.785173 秒的垃圾回收暂停事件。发生很长时间的垃圾回收暂停事件并非罕见,但这一次的事件却与你常常可以看到的那些不同。与大多数日志不同的是,这份日志中包含了足够详细的信息,它足以让我确认是 JVM 外部的一些东西导致了这次暂停,如果我们真的要把它当作一次暂停事件的话。

我把这份日志给 Heinz Kabutz 博士看了,他问道:“这是不是在 386 上跑出来的?”当然了他是在和我开玩笑,但这个问题却很有意义。尽管有许多文章详细讨论过垃圾回收的原理,大多数却仅仅是在讨论一些最基础的分析方法而已,很少有能超过这个范畴很多的。

分析的方法可以让我们看得更深入,了解是什么出错了,以及我们该做些什么来保证它不再发生。咱们再一起深入地查看一下这份日志,看看为什么我说垃圾回收机制其实并不是这次“垃圾回收暂停”事件的起因。

在这个案例中,日志文件异乎寻常的小,里面包含了 22 次垃圾回收事件,却只有 678 行日志。大多数分析师在查看这么小的日志的时候都给不出什么有价值的分析结果。相反,另一份同一天收到的日志却更有代表性,它里面包含了 14110 次垃圾回收事件,藏在了 959962 行日志中。除了要处理的日志量带来的问题之外,还有大概 60 种不同的 JVM 参数会影响垃圾回收日志的格式,而且每种不同的参数组合都会产生各自不同的影响。除此之外,这些日志的格式还会不断地以各种意想不到的方式发生变化。

还有,在 Java 9 之前,垃圾回收日志并不是线程安全的,这意味着如果你使用了并发回收器的话,日志有可能是损坏的。把这些因素放在一起考虑,你就会发现你面前要分析的日志不管对于人类还是机器来说,都是不可读的。

我发起研发 jClarity 的 Censum 垃圾回收日志分析工具的初衷,就在于我希望可以尝试去做分析,还有我的确需要可以从尽可能多种不同的数据源中得到垃圾回收日志,并抽取到有用的信息。

在这个例子中我们用的是推荐参数的最小集合:

复制代码
-Xloggc:gc.log
-XX:+PrintGCDetails
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime

做分析最简单的方式就是直接把日志导入 Censum 之中。图一所展示的就是所有运行过的分析的总结。首先要注意的是,在各项分析中有两项是失败的,并用红色的打叉图标做出了标记。再看看这些分析结果,就会知道 Censum 已经对情况做出了判断。要把失败了的分析手动重做一次只要一分钟就可以了,但首先咱们还是先看看 Censum 的分析总结,以及“Heap after GC”图表。

(点击放大图像)

图一:分析总结

两个视图都可以帮我们了解两次失败了的分析的背景。从图二的总结页面可以看出这里使用了并发回收器。共有21 次年轻代回收和一次完全回收。这个视图中的其它指标都没有太多好说的,或者说是在可接受的范围之内。比如应用程序99.3% 的吞吐量是远高于要求的95% 的阈值的,而每秒1165KByte 的分配率也是远低于每秒1GByte 的阈值。如果超出了前一个阈值,就意味着垃圾回收机制配置得不够好(那种情况下会触发我们称之为垃圾回收器的“技术调节”的事件,但那是另一篇文章的主题),而如果超出了后面的阈值则意味着你的应用程序内存不足了,应该判断并优化内存分配的热点,当然这又是再一篇文章的主题了。

(点击放大图像)

图二:Censum 总结视图

这个例子中的“Heap after GC”页表明堆也成了完整的垃圾回收过程的一部分。别的回收过程看起来都没什么,堆的使用情况看起来也还很稳定。总的堆大小在700MB 到800MB 之间波动。Tenured 相关的大小也很平稳的保持在520MB。这表明年轻代的大小在200MB 到300MB 之间。按照现在的标准来看,这些内存池的大小都特别小,所以Heinz 才会开那个386 的玩笑。这份日志中没有任何东西提到“23 秒的暂停”。咱们再去看看那些失败了的分析,看看为什么会失败,看看这是不是导致长期暂停的原因。

第一个失败了的分析是简单的暂停时间分析。因为暂停的时间太长了,所以它才会失败。考虑到总的花费比较小(在这次分析中叫做“Time Paused”),这次长时间的暂停就表现得特别显眼,需要深入调查。

因为PrintGCDetails 被打开了,所以我们可以注意到一条有价值的信息,就是垃圾回收线程的CPU 利用率。这些总结包括垃圾回收线程在回收过程中消耗的用户态时间、内核态时间和实际时间等。这就是“High Kernel times”分析要使用的数据。图三显示了这个视图。

(点击放大图像)

图三:High Kernel times 视图

CPU 总结中的对高内核态时间的分析为解决问题提供了许多启示,可以鉴别出值超出了正常范围的用例。这些分析让开发者们可以以各种不同的专业水平去查看垃圾回收日志,看出那些他们可能遗漏了的问题。人们在查看这些图表时可能碰到的问题仅仅是不熟悉这些数据的展示方式而已。即使数据展示得很恰当,也有可能会包含一些让人困惑的信息,或者掩盖掉某些不同的条件。就算没有这些信息来打扰,我们人类也会带着偏见来看这些结果,总是想要看到些并不存在的东西。用启发式算法可以有助于克服主观的推断。回到我们的例子上来,通过查看用户态时间和实际时间的测量值,我们就可以估计出垃圾回收周期内的并行度。我们也可以判断出垃圾回收线程累积的内核态时间是什么时候超出正常范围的。

每个线程都维护着若干个性能计数器,用于跟进用户态和内核态时间等不同的指标。垃圾回收器会从各个不同的垃圾回收线程中收集这些计数器的值,并在垃圾回收日志记录的末尾打印总结信息。操作系统会维护这些性能计数器的值。它会在每个采样点进行采样,判断每个线程当前运行在什么模式下,进而相应地增加用户态或内核态计数器的值。一般来说垃圾回收线程会运行在用户态模式下,这意味着大多数的计数都应归于用户态模式下。然而,当垃圾回收线程执行系统调用时,它就会进行模式转换,成为一个内核态线程。当线程运行在这个保护模式之下时,所有的采样值都会属于内核态时间。这些系统调用正常情况下都会是在请求内存中的某个页面,或者将某些热点性能数据落盘。但因为请求一个新的页面本应很快,而写出的热点数据量也应该是极少的,所以垃圾回收线程的内核态时间计数值不应该很高。可是,如果出于某些原因,这些操作执行的时间超出了期望的时间,这些线程计数器就会计下更多的内核态时间值。

比如,一种常见的情况是当一台物理机上运行了多个 JVM 时,它们每一个都会打印出非常大量的日志。在这个案例中,IO 通道特别慢,它拖累了垃圾回收线程执行写系统调用的时间。在这期间进行的任何 CPU 采样都会归于内核态时间。而当内核在管理页面时,别的垃圾回收线程也会被记录进内核态模式。当然,这本不该导致垃圾回收线程积累太多的内核态时间,但在系统内存不足的时候,内核态时间就会增加了。这些问题都可能会干扰回收器的执行,但它们也会影响你的程序的整体执行性能。对内核态时间简单地查看一下,就可以帮助我们判断是否有什么干扰了回收器的执行,让它没办法在短时间内完成应该完成的任务。

在我们的案例中,Censum 也只在一个用例中报告了内核态时间高。另外,它还报告收集器曾有 18 次是单线程运行的。因为总共有 22 次回收任务,这就足以证明回收器不是导致这次长时间暂停的主要原因。

看看图四中的 CPU 总结视图,就可以很快地在左上角发现这次长时间暂停事件。回收发生在一堆事件的中间,因此很难把所有情况都了解明白。而且也很明显,实际执行时间远比用户态时间或内核态时间长,这很奇怪。

(点击放大图像)

图四:CPU 总结

当放大图四时我们可以看到,大概有0.08 秒的用户态时间和0.02 秒的内核态时间。现在的问题是,0.08 + 0.02 = 0.10 秒的用户态和内核态时间和怎么能对应上23 秒的实际时间,即使假设0.08 秒的用户态时间都是由单线程累积起来的也不行。(在多线程的情况下就会更混乱,因为这0.08 秒还要被多个线程去均分。)通常在多线程的情况下,多个线程积累的用户态时间会比实际时间更长,所以这个情况看起来非常混乱。很明显系统中发生了什么事,让垃圾回收线程无法运行完22 秒。换句话说,JVM 之外的某些东西导致了这次暂停,在这个案例中是操作系统维护,也就是说垃圾回收日志转移了我们的视线而已。

(点击放大图像)

图五:CPU 总结放大图

结论

Lawrence Kraus 曾经说过,“有两种我特别喜欢的状态,就是困惑和出错,因为只有在这些情况下才会有学习的机会”。在这个案例中,我们开始误以为是垃圾回收操作是这次长时间暂停的原因,但在我们认真查看了垃圾回收日志之后,我们才纠正了一出现长时间暂停就去埋怨垃圾回收器的偏见。在我们归咎于垃圾回收器时,最正常的反应就是上网搜贴子,查看是不是有哪个偏门的 JVM 参数可以帮忙解决这样莫名其妙的问题。在做这样的分析时,我们却直接发现垃圾回收器不是主因,那接下来要问的问题就是,排除了垃圾回收器之后,又该调查谁?

上面发现的证据表明,在出现暂停时的绝大部分时间内,垃圾回收线程并不活跃。如果暂停是后台的 IO 操作导致的,那由操作系统调用的垃圾回收线程就该累积起很长时间的内核态时间才对,但事实上并没有。这些都排除了垃圾回收线程的可能性,而且它们甚至长达 22 秒没被调用过。如果我们的程序不是被垃圾回收线程拖累的,那尽管看起来说不通,剩下唯一的可能性就是 JVM 是被操作系统暂停的。

事实是,操作系统时常也会自动做维护,在做维护时,表象和垃圾回收线程一样,操作系统也会将所有其它事务暂停。就像调校好的垃圾回收器也会出现暂停一样,操作系统暂停也必然会不定期地发生,而且几乎不会被注意到。有的时候暂停也会持续很久。比如,据说有一次一个垃圾回收器的页面分配请求等了几秒钟才被处理到。在这些案例中,通常原因都是操作系统正在将内存中的数据移来移去,以便得到足够的空闲内存去满足将来的请求。

尽管在这个案例中看起来不像,我们以前也有好几次发现网络时间协议(Network Time Protocol,NTP)在垃圾回收的过程中调整时钟。一般在时钟向前调整的时候我们才能确认是这种情况,日志会显示垃圾回收先结束后开始。如前所述,这在这个案例中也还是不太可能,因为时钟调整一般每次都只调整几微秒,不会一次调整几十秒。

总之,最重要的发现就是对垃圾回收过程的分析可以帮助我们避免直觉性地怪罪垃圾回收器,让我们可以视野更开阔些,发现些更重要的东西,帮助我们提升应用程序的性能和稳定性。

关于作者

Kirk Pepperdine是最早 Java 性能调校研讨会的发起者,他曾经分析过垃圾回收原理和许多其它的 Java 性能问题。他也与别人一起成立了 jClarity,这家公司研发的是用机器学习技术支撑的性能分析工具,以此来帮忙诊断复杂的性能回退问题。

阅读英文原文 Want to Know What’s in a GC Pause? Go Look at the GC Log!

2017-05-16 17:114702
用户头像

发布了 152 篇内容, 共 70.7 次阅读, 收获喜欢 64 次。

关注

评论

发布
暂无评论
发现更多内容

云原生时代如何用 Prometheus 实现性能压测可观测-Metrics 篇

阿里巴巴云原生

阿里云OSS图床搭建

懒时小窝

阿里云 OSS 图床

浏览器突然好用多了。。。

Jackpop

你真的会用搜索引擎吗?

Jackpop

基于 KubeVela 的机器学习实践

阿里巴巴云原生

阿里云 开源 容器 云原生 KubeVela

OpenKruise v1.1:功能增强与上游对齐,大规模场景性能优化

阿里巴巴云原生

阿里云 容器 云原生 OpenKruise 套件

Linux驱动开发-编写(EEPROM)AT24C02驱动

DS小龙哥

4月月更

王者荣耀商城异地多活架构设计

Geek_8d5fe5

「架构实战营」

[Day6]-[动态规划] 俄罗斯套娃

方勇(gopher)

LeetCode 数据结构和算法

赶紧给你的文件加个密吧!

Jackpop

明道云如何实现银行内部评级管理

明道云

为什么他们选择阿里云容器服务 ACK

阿里巴巴云原生

阿里云 容器 ACK 合作 阿里云云原生

王者荣耀商城异地多活架构设计

「架构实战营」

【图解数据结构】栈全面总结

知心宝贝

c++ 数据结构 算法 4月月更

Linux之ssh-add命令

入门小站

读《A Philosophy of Software Design》(01-07)

术子米德

架构师成长笔记

让页面跳转更有趣 —— 实现自定义页面切换转场动画

岛上码农

flutter 移动端开发 4月月更 跨平台开发 安卓 ios

初创者的精神和领导力--Coursera学习笔记(27/100)

hackstoic

领导力 创业者

OpenYurt 之 Yurthub 数据过滤框架解析

阿里巴巴云原生

阿里云 开源 容器 云原生 边缘计算

通达系统架构设计文档

小锅米线

MapReduce 学习思考

en

mapreduce

在线时间加减计算器

入门小站

计算器

在线XML压缩工具

入门小站

工具

体验一款基于AI和区块链的体感运动App(26/100)

hackstoic

NFT 区块链、 gamefi P2E

即学即会 Serverless | 如何解决 Serverless 应用开发部署的难题?

阿里巴巴云原生

阿里云 开源 Serverless 云原生 Serverless Devs

大话后端开发的奇技淫巧大集合

SFLYQ

架构 Web 后端 服务端 经验分享

Rust的对象安全性

Shine

rust

剑指Offer之面试题57: 和为s的数字

宇宙之一粟

算法刷题 4月月更 剑指Offer

恭喜我的同事丁宇入选年度 IT 领军人物

阿里巴巴云原生

阿里云 开源 云原生 获奖

flink维表查询redis之flink-connector-redis

山里小龙

王者荣耀商城异地多活架构设计

孙强

#架构师实战

想知道垃圾回收暂停的过程中发生了什么吗?查查垃圾回收日志就知道了!_Java_Kirk Pepperdine_InfoQ精选文章