我可能在跑一个假GC

最近在学习Hotspot的各种性能参数,从JIT到GC,毕竟我们的产品是一个heavily tuned的Java服务,多知道一些没坏处。

我是一个比较龟毛的人,虽然像-XX:+UseParallelOldGC和-XX:+UseParNewGC这样的写法已经让一个处女座强迫症程序员没法活了,但是后来的事情还是让我屎尿未及的。

Survivor Space Sizes

你们都知道,Young Generation的部分可以分为Eden Hazard和Survivor Spaces。Eden多大Survivor Spaces多大的是通过这么几个参数来控制的:

-XX:InitialSurivorRatio=N
-XX:MinSurvivorRatio=N
-XX:SurvivorRatio=N

那你想你要怎么设置这些ratio呢?假如你刚学了怎么控制Young Generation(New Generation)和Old Generation(Tenured Generation)之间的比例,就知道可以用一个参数来控制两者之间比例:

-XX:NewRatio=N

这个参数本身就是很扯淡一件事,因为-XX:NewSize=N是设置的Young Generation的大小,那么你就应该期待NewRatio会是指的Young Generation在整个Heap中的比率。然而并不是那么简单,比如默认值2,指的是假如Old Generation占2的话,Young Generation占1。同理,如果设为4,就是Old Generation占4/5,而Young Generation占1/5。所以这个值,明明是Young Generation是1的话,Old Generation的ratio。那么到底应该是叫NewRatio,还是应该叫OldRatio呢?

其实"他们"号称是这样的算的:

New-Ratio = 1 / (NewRatio + 1)  

这件事本来就已经非常"妈的智障"了。有了这个铺垫,我就防着"他们"再来这一套了。果然,这三个关于survivor space的计算方式是类似的,比如默认值-XX:InitialSurvivorRatio=8,那么其实是,呃,Eden占8/10,然后两个Survivor Spaces各占1/10。。。所以,其实每一个survivor space的ratio是:

Init-Survivor-Space-Ratio = 1 / (InitialSurvivorRatio + 2)  

所以这个值应该叫InitialEdenRatio,对么。。。

然而,事情到这里并没有完。因为adaptive sizing policy(-XX:+UseAdaptiveSizingPolicy)如果还在的话,这时候你就需要控制Survivor Space最大能有多大。然后我就不得不引用Java Performance: The Definitive Guide这本书的原话来描述这件事儿了:

Note again that the value is a ratio, so the minimum value of the ratio gives  
the maximum size of survivor space. The name hence a little counterintuitive.  

你们Java界就是这么理解ratio的么。哪怕你们觉得ratio 3/1和ratio 1/3是一回事,呃,这里如果你给的值是3,呃,得到的值也是莫名其妙的1/5啊!另外,就哪怕觉得没事儿,你们这么最小比率,呃,获取最大值的做法,呃,自己人都知道是counterintuitive啊。所以,这三个参数是不是都应该叫EdenRatio呢?是不是!是不是!

然而,事情到这里还没有完。这时候要控制Survivor Space到底设置成多大,是由一次New Gen GC之后,Survivor Space用掉了多少空间决定的。这个参数叫做:

-XX:TargetSurvivorRatio=N

你们看,它和那三个参数长得有多像!有多像!有多像!那么它也是通过"那种办法"计算的么?呃,并不是。它是个百分比!!!你们他妈的是跟我说:SurvivorRatio,MinSurvivorRatio,InitialSurvivorRatio是个你们那种没道理的公式算出来的,而TargetSurvivorRatio是个百分比!!!???

你们说,很直观得让所有这些参数都是一个intuitive的百分比,到底有多难???有多难???

Tenuring Threshold

这时候,你还要控制live objects在Survivor Spaces里面能够存活几个GC周期。这里用到两个参数叫做:

-XX:InitialTenuringThreshold=N
-XX:MaxTenuringThreshold=N

首先,这个值讲的是survive了多少个GC cycle之后,触发了tenure。我们都是说今天老子跑了多少圈,不会说去老子今天多少圈之后到终点。因为这么说谁知道你是跑了多少圈,还是游了多少圈,还是去了多少个猪圈。因此怎么都是叫SurvivalThreshold或者SurvivalLimit要直观得多。尤其是Max Tenuring Threshold这个说法,这个max值和tenuring这个动作一点屌关系没有,它并不是tenuring这个动作的max。这样的描述,不管是英语还是汉语,都是要绕一圈,加上这件事的背景知识,才能理解。

到了后来介绍-XX:+AlwaysTenure和-XX:+NeverTenure的时候,事情就更麻烦了。前面提到那本书的作者不得不单独拉出来两三段,去解释AlwaysTenure是在survivor space里面一个cycle也活不过去直接去Old Generation;而NeverTenure的话是只要survivor spaces不塞满,live objects一直在survivor spaceS里待着等young generation的collector收了他们。

根本上讲,这件事关心的是因,和触发这个因的条件。而你们用这件事的结果去命名这个参数,当然绕死了。。。

起名字的能力

我上大二的时候,我们系副主任陈建明老师讲离散数学的时候跟我们说:做软件开发,起名字的能力,是最重要的能力。后来那年暑假,我和我的在浙大读计算机系的好友提起来,他笑着说他们离散数学老师也是这么讲的。

起名字的能力,包含了一个开发者对于这个逻辑的认知,和对于如何把这个逻辑表达出来的能力,这两点做不到,能写出来的东西也就比较堪忧了。在起名字的时候又完全不考虑前后的一致性,你们他妈的还想不想让人用了?所以那么多人觉得Java的GC tuning很头疼,我倒觉得是一大半功夫花在理解这些参数到底什么意思、怎么计算和设置上了。

因此Java可以在7里面把Generics的type inference设计反,能在8里面把Lambda实现得那么蹩脚,呃,也就一点不奇怪了。。。(光速逃