【第五期:Java网络服务的垃圾回收性能调优】
Java网络服务的垃圾回收性能调优
以下内容主要来自于对(Tuning Java Garbage Collection for Web Services)这篇文章的翻译,建议大家先读原文,自己的翻译水平太有限
作为java垃圾回收领域的专家,我不厌其烦地一遍又一遍地回答相同的问题:“为什么我的服务会有频繁的GC停顿?”;“如何去避免长时间的GC停顿?”;“并发模式(应该指CMS中的并发标记)失败的后果有多严重?”。本着授人以鱼不如授人以渔的精神,我写下这篇文章来教你如何进行垃圾回收调优,以使得你的服务可以像LinkedIn一样避免长时间垃圾回收而流畅运行。请注意,我们调优对象是类似于LinkedIn一样的服务,且我们将对程序如何使用内存做出一些假设。基本原则将具有普适性,但如果使用内存的方式与假设不同的话,则下面讲的技术和分析方法是失效的。
值得庆幸的是,我们会简单地选择使用CMS作为垃圾回收算法(因为在大部分情况下,默认的就是最好的),在大部分情况(非所有情况)下,默认方法的性表现的都很出色。在高负载的情况下,这也是你最怕出现问题的时候,默认方法和参数将表现不太理想(strugling(不知如何翻译是好!!)),幸运的是,当你认真排查full GC日志的时候,很很容易地找出问题所在。
如何阅读GC日志
Young GC(大部分情况下).
年轻代有两个survivor区,但每次只会使用一个survivor。survivor可存着上一次垃圾回收后幸存的对象,之所以没有将它们放入老年代,因为我们认为他们(大部分)会很快会消亡(不可达),而在老年代回收他们的代价将变得非常高。值得注意的是,对于经过一至两次垃圾回收就会消亡的对象,虽然放在suvivor区进行回收代价也是挺高的,但相对于放在老年代回收要好很多。对于LinkedIn的服务,大部分内存使用都是在外部请求的进程中消耗的,经过两至三次垃圾回收,再将存活的对象放入老年代表现得最好(这句不知道如何翻译,就按照自己的理解了)
CMS
CMS是很复杂的,因此我们忽略了很多不重要的内部,为了达到我们的目的,我们只需要关注几个重要的阶段。
在并发标记清理(concurrent mark and sweep)有很多内容,但并不重要。大部分情况下,我们更关注,full GC 何时开始(老年代占比多少时触发full GC)以及full GC需要花多长时间。在垃圾清理完成之前,如果老年代新生的对象占满了内存,则虚拟机则会在GC完成之前发生停顿。如果由于历史的原因,在使用单线程的垃圾回收器,通常这并不是一个好的选择…….
如何使用GC日志数据
跳过前面内容的读者,需要从现在开始认真阅读了。带着你刚学的如何阅读GC日志的知识,我们将计算以下6个参数(参数都将简写,为方便编辑公式):
1.对象分配速率Allocation Rate(简写为AR):年轻代的大小除以两次Young GC之间的间隔
2.提升速率The Promotion Rate(PR):老年代的增长速度.
3.suvivor死亡率The Survivor Death Ratio(SDR):第N代suvivor的大小除以第N-1代的大小,
4.老年代垃圾回收时间Old Gen collection times(OGCT):从CMS -initial-mark 到 concurrent-reset的时间,即一次FULL GC时间,你需要了解平均值和最大值.
5.Young GC时间(YGCT):平均值和最大值.
6.老年代缓冲区大小(OGB):PR*max(OGCT)(1+a).
我经常提醒大家既然内存是由项目分配置的,那GC调优就要调整项目本身。值得注意的是,项目调整主要是调整分配速率和SDR。
以上这些指标为我们调整堆起了重要的引导作用。首先,你要确定在一次CMS后,OGB得到了清理,否则你在高负载情况下你将遇到并发模式失败。
几种调优的方向
只要满足基本条件,我们可以通过以下三件事情(技术上我们可以增大或减小这些参数,但基本都是增大这些参数)来改善GC效率.
1.增加年轻代的大小:这样,一个垃圾回收之前,将容纳更多的垃圾对象的产生,而且将会降低Young GC 的执行频率。假设你的服务分配内存只依赖于外部请求,增大Young的大小并不会影响YOUNG GC 复制到suvivor区Objects的数量(大小更合适),因此会降低PR。但是这会让Yong GC 的时间变得更长,如果Young GC的时间已经达到了你的可容忍度,你就要好好考虑了。
2.增加最大的surviror age:这会使得更多对象在被移至老年代之前死亡,将降低老年代的对象的增长速率并降低full GC执行次数,且OGB所需要的大小也可以降低。这也意味着你需要增加survivor空间大小,否则当survivor发生溢出时,就会将所有的对象拷贝至老年代,而且会增加survivor区对象的拷贝的时间。当调.整survivor age时,需要监控SDR,当它达到50%时(为何是这个?),应该将其移至老年代。
3.增加老年代的大小:这无疑会降低fullGC的频率,幸运的是CMS 停顿(初始标记阶段和再标签阶段)的时间会相对稳定(为啥会稳定?),这一特点将保证你的服务不会在高负载情况下产生内存溢出。
总结:
调整垃圾回收器有以下原则:
1.首先确定你的服务是否需要调优,如果停顿时间和频率都没有问题,一般你不需要调整.
2.如果AR或者PR有问题,请重写代码,不要随意在代码中分配内存.
3.确定你需要改变的目标,然后做出改变,并确定是否达到了你的目标。
我自己推导了几个重要的公式,但不知道怎么在markdown上编辑,如果有兴趣可以找我讨论一下