关于java:记一次真实的JVM性能调优过程

7次阅读

共计 2335 个字符,预计需要花费 6 分钟才能阅读完成。

背景

最近对负责的我的项目进行了一次性能优化,其中包含对 JVM 参数的调整,算是进行了一次简略的 JVM 调优,JVM 参数调整之后,服务的整体性能有 5% 左右的晋升,还算不错。

先介绍一下我的项目的根本状况:

我的项目是一个高 QPS 压力的 web 服务,单机 QPS 始终维持在 1.5K 以上,因为旧机器的”连累”,配置的堆大小是 8G,其中 young 区是 4G,垃圾回收器用的是 parNew + CMS。

旧状

首先是查看以后 GC 的状况,次要是应用 jstat 查看 GC 的详情,再查看 gc log,剖析单次 gc 的具体情况。

应用 jstat -gcutil pid 1000 每隔一秒打印一次 gc 统计信息。

能够看到,单次 gc 均匀耗时是 60ms 左右,还算能够承受,但 YGC 十分频繁,基本上每秒一次,有的时候还会一秒两次,在一秒两次的时候,服务对业务响应时长的压力就会变得很大。

接着查看 gc log,打印 gc log 须要在 JVM 启动参数里增加以下参数:

  • -XX:+PrintGCDateStamps:打印 gc 产生的工夫戳。
  • -XX:+PrintTenuringDistribution:打印 gc 产生时的分代信息。
  • -XX:+PrintGCApplicationStoppedTime:打印 gc 进展时长
  • -XX:+PrintGCApplicationConcurrentTime:打印 gc 距离的服务运行时长
  • -XX:+PrintGCDetails:打印 gc 详情,包含 gc 前 / 内存等。
  • -Xloggc:../gclogs/gc.log.date:指定 gc log 的门路

看到的 gc log 形如:

单次 GC 方面并不能间接看出问题,但能够看到 gc 前有很屡次 18ms 左右的进展。

剖析和调整

YGC 频繁

间接查看 gc log 并不直观,咱们能够借用一些可视化工具来帮忙咱们剖析,[gceasy](https://gceasy.io/) 是个挺不错的网站,咱们把 gc log 上传上去后,gceasy 能够帮忙咱们生成各个维度的图表帮忙剖析。

查看 gceasy 生成的报告,发现咱们服务的 gc 吞吐量是 95%,它指的是 JVM 运行业务代码的时长占 JVM 总运行时长的比例,这个比例的确有些低了,运行 100 分钟就有 5 分钟在执行 gc。幸好这些 GC 中绝大多数都是 YGC,单次时长可控且散布均匀,这使得咱们服务还能安稳运行。

解决这个问题要么是缩小对象的创立,要么就增大 young 区。前者不是一时半会儿都解决的,须要查找代码里可能有问题的点,分步优化。

而后者尽管改一下配置就行,但以咱们对 GC 最直观的印象来说,增大 young 区,YGC 的时长也会迅速增大。

其实这点不用太过放心,咱们晓得 YGC 的耗时是由 GC 标记 + GC 复制 组成的,绝对于 GC 复制,GC 标记是十分快的。而 young 区内大多数对象的生命周期都十分短,如果将 young 区增大一倍,GC 标记的时长会晋升一倍,但到 GC 产生时被标记的对象大部分曾经死亡,GC 复制的时长必定不会晋升一倍,所以咱们能够释怀增大 young 区大小。

因为低内存旧机器都被换掉了,我把堆大小调整到了 12G,young 区保留为 8G。

分代调整

除了 GC 太频繁之外,GC 后各分代的均匀大小也须要调整。

咱们晓得 GC 的晋升机制,每次 GC 后,JVM 存活代数大于 MaxTenuringThreshold 的对象晋升到老年代。当然,JVM 还有动静年龄计算的规定:依照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的降职年龄阈值,但看各代总的内存大小,是达不到 survivor 区的一半的。

所以这十五个分代内的对象会始终在两个 survivor 区之间来回复制,再察看各分代的均匀大小,能够看到,四代以上的对象曾经有一半都会保留到老年区了,所以能够将这些对象间接晋升到老年代,以缩小对象在两个 survivor 区之间复制的性能开销。

所以我把 MaxTenuringThreshold 的值调整为 4,将存活超过四代的对象间接晋升到老年代。

偏差锁进展

还有一个问题是 gc log 里有很多 18ms 左右的进展,有时候间断有十多条,尽管每次进展时长不长,但间断屡次累积的工夫也十分可观。

这个问题在我之前的文章有讲过,这里就不赘述了。1.8 之后 JVM 对锁进行了优化,增加了偏差锁的概念,防止了很多不必要的加锁操作,但偏差锁一旦遇到锁竞争,勾销锁须要进入 safe point,导致 STW。

解决形式很简略,JVM 启动参数里增加 -XX:-UseBiasedLocking 即可。

后果

调整完 JVM 参数后先是对服务进行压测,发现性能的确有晋升,也没有产生重大的 GC 问题,之后再把调整好的配置放到线上机器进行灰度,同时收集 gc log,再次进行剖析。

因为 young 区大小翻倍了,所以 YGC 的频率减半了,GC 的吞量晋升到了 97.75%。均匀 GC 时长略有回升,从 60ms 左右晋升到了 66ms,还是挺合乎预期的。

因为 CMS 在进行 GC 时也会清理 young 区,CMS 的时长也受到了影响,CMS 的最终标记和并发清理阶段耗时减少了,也比拟失常。

另外我还统计了对业务的影响,之前因为 GC 导致超时的申请大大减少了。

小结

总之,这是一次挺胜利的 GC 调整,让我对 GC 有了更深的了解,但因为没有深刻到 old 区,之前学习到的 CMS 相干的常识还没有温习到。不过性能优化并不是久而久之的事,须要时刻关注问题,及时做出调整。

我把本人的调优教训整顿成了一本 PDF,也收集了一些对于调优的书籍的 PDF,都分享给大伙,须要的同学能够点击 JVM 调优笔记与学习书籍 支付。

对于本文有什么疑难能够在上面留言交换,如果您感觉本文对您有帮忙,欢送关注。

正文完
 0