有许多现成的调优经验的介绍。Charlie Hunt写的《Java Performance》一书里有很详细的介绍。


其中涉及GC调优的部分在过往的JavaOne里也有session介绍过。请搜这个标题:"Step-by-Step: Garbage Collection Tuning in the Java HotSpot™ Virtual Machine" 



不过那种很具体的现成经验毕竟是别人在他们见过的环境里沉淀下来的,并不一定适用于所有情况。所以怎样的调优方法适合自己,还是得理解了系统底层的工作原理然后再在实际环境里加以应用、变通才好。 



对HotSpot VM里的GC不熟悉的,至少应该把

Sun以前出的HotSpot VM的GC调优白皮书读了。 



==================== 



为啥HotSpot VM里收集有两种概念,一种是young GC/minor GC,另一种是full GC/major GC;为啥后者不是叫old GC? 



JVM <wbr>年轻代和年老代 <wbr>大小设置


因为young GC只收集young gen,但full GC会收集整个GC堆。 


HotSpot VM的full GC会收集整个Java堆,包括其中的young gen与old gen;同时也会顺便收集不属于Java堆的perm gen。 


Young + old + perm构成了HotSpot VM的整个GC堆。至少目前还是这样。 


(JDK8里的HotSpot VM就没有perm gen了。请注意。) 



CMS在并发模式工作的时候是只收集old gen的。但一旦并发模式失败(发生concurrent mode failure)就有选择性的会进行全堆收集,也就是退回到full GC。 



==================== 



大小分配怎样才合理取决于某个具体应用的对象的存活模式。 



这涉及到分代式GC的原理。最初为何要把GC堆划分为多个区域,以不同的频率来收集它们?本来就是为了让每次收集的效率都最大(在收集的区域里尽可能回收出可用空间)。如果一个应用里对象的存活模式满足弱分代假设,那么把新生对象放在同一个区域里,每次收集这个区域的效率都应该比较高(因为假设是新生对象活不了多久就死了)。 



有人专门研究这个。可以用"java object demography"这组关键字来搜已有资料。 



==================== 



举例:可能很多人都有一种印象,young gen应该比old gen小。笼统说确实如此,因为在最坏情况下young gen里可能所有对象都还活着,而如果它们全部都要晋升到old gen的话,那old gen里的剩余空间必须能容纳下这些对象才行,这就需要old gen比young gen大(否则young GC就无法进行,而必须做full GC才能应付了)。 


实际上却不总是这样的。所谓“最坏情况”在很多系统里是永远不会出现的。调优就是要针对实际应用里对象的存活模式来破除这些“最坏情况”的假设带来的限制。 



许多Web应用里对象会有这样的特征: 


·(a) 有一部分对象几乎一直活着。这些可能是常用数据的cache之类的 

·(b) 有一部分对象创建出来没多久之后就没用了。这些很可能会响应一个请求时创建出来的临时对象 

·(c) 最后可能还有一些中间的对象,创建出来之后不会马上就死,但也不会一直活着。 



如果是这样的模式,那young gen可以设置得非常大,大到每次young GC的时候里面的多数对象(b)最好已经死了。 


想像一下,如果young gen太小,

每次满了就触发一次young GC,那么young GC就会很频繁,或许 很多临时对象(b)正好还在被是使用(还没死),这样的话young GC的收集效率就会比较低。要避免这样的情况,最好是就是把 young gen设大一些。 


那old gen怎么办?如果是上面说的情况,那old gen至少要足以装下所有长期存活的对象(a);同时也要留出一定的余地用来容纳

young GC没能清理掉的临时对象。 


这样,最后调整出来的结果很可能y

oung GC反而比old gen大许多。这完全没问题。 


只有(a)和(b)的话就完美了,现实中最头疼的就是针对(c)对象的调优。它们或许会经历多次young GC之后仍然存活,于是晋升到old gen;但晋升上去之后或许很快就又死掉了。 


这种对象

最好能不让晋升到old gen(可以让它们在survivor space里多来回倒腾几次再晋升,也就是想办法增加 tenuring threshold;不过HotSpot VM里的GC不让外界对此多插手,想减小MaxTenuringThreshold很容易,想增加实际有效的tenuring threshold就没那么容易了)。但如果真的不让它们晋升,young GC的暂停时间就会增长(在survivor space里来回倒腾对象意味 着要来回拷贝,这会花时间)。 

所以有一种策略是尽量让这种对象的

大部分在young GC中消耗掉(在保持young GC的暂停时间不超过某个预期值的前提下),而“漏”到old gen的那些让诸如 CMS之类的并发GC来解决。 

总之这里要做一定的tradeoff就是了。 



================= 



知道了原理之后在现实中要如何实践呢? 



首先得了解硬性限制:

某个服务器总共有多少内存,其中最多可以分配多少给某个应用程序;有没 有一些服务对响应时间有严格要求,有的话限制是多少,之类的。 


然后看看应用的特征是怎样的。可以借助一些工具来了解对象的存活情况,例如NetBeans的

profiler就有这样的功能( 老文档);许多其它主流Java profiler也有类似的功能。 


这些工具的精度和性能开销各异,总之自己摸索下看看吧。 



情况了解清楚了就可以开始迭代调整各种参数看实际运行的表现如何。迭代到满意为止。 


要分析实际GC的运行状况,首要切入点就是

分析GC日志。有很多工具能把HotSpot VM的GC日志可视化。我以前一直在用的是这个: GCHisto。 


然后像Twitter做的这个工具也可以抽取一些GC的辅助统计信息:

https://github.com/twitter/jvmgcprof 



================= 



那个…上面随便写了些。文字不通顺的地方请轻拍,要整理成“文章”的话又要烧脑细胞了… 



没说清楚的地方请另外补充背景知识… 


例如这个:

Attila Szegedi的GC调优经验。 


还有这个:

Gil Tene谈GC。



 

 


如果老年代设置过小,就会频繁触发full gc,full gc是非常耗时的。年轻代在经过n(hotspot默认是15)轮后会进入老年代,这样老年代顶不住了,就会触发full gc,回收时需要stop the world,这样系统经常发生长时间停顿,影响系统的吞吐量