《Java效能優化權威指南》的邊邊角(5)——被我們搬運的豆瓣讀書筆記

丁曉昀發表於2014-02-14
感謝@RednaxelaFX授權轉載

原書頁碼:第110頁

A full garbage collection generally expresses the notion of garbage collecting and compacting the old generation and permanent generation spaces.

首先在頂上說full GC“通常”是collect and compact old and perm gen就不對。

緊接著的這句就對了:

In the HotSpot VM, the default behavior on a full garbage collection is to garbage collect the young generation, old generation, and permanent generation spaces.

實際上HotSpot裡的Full GC是collect the whole heap,包括young、old、perm的(就跟原文這句說的一樣),而且generally也應該是指收集全堆(跟前面那句說的不一樣)。

隨便找條Full GC的日誌就能看出來:

[Full GC [PSYoungGen: 13809K->0K(505216K)] [PSOldGen: 253802K->245481K(319488K)] 267612K->245481K(824704K) [PSPermGen: 70059K->70059K(118784K)], 0.5869143 secs] [Times: user=0.59 sys=0.00, real=0.59 secs]

可以看到Full GC過後young gen有收集(13809K->0K),old gen有收集(253802K->245481K),perm gen也有收集(70059K->70059K)看起來像是沒動但實際上可能:

  1. 收集了少於1K的東西所以在K為單位的日誌上看補出來;
  2. 這次GC時perm gen裡的物件大部分都真的還是活的)。

原因很簡單:每當需要做GC的時候,如果收集全堆就能遍歷整個物件圖,這樣能保證知道準確的物件可到達資訊;但如果只收集一部分(例如只收集young或old/perm),就不能只遍歷這個部分,而必須要遍歷額外的部分才足以確定遍歷到了足夠大的物件圖,否則就可能漏掉活著的引用而誤將活物件給收集了。在做minor GC時,只收集young gen,這額外的部分就是覆蓋old/perm的card table/remember set,也就是說為了做minor GC其實還得掃描一部分old/perm,作為根集合的一部分。

那想像一下如果只收集old/perm會怎樣。很簡單,那就是young gen的一部分也要被掃描,也就是young gen(至少其中一部分)被作為GC的根集合的一部分。通常young gen被配置得比較小,掃描它的一部分跟掃描整個young gen其實也差不了多少。所以“只收集old gen”通常並不划算,還不如直接整個堆收集。

一個例外是CMS的併發收集階段。它真的只收集old(可選是否收集perm),而不收集young。因此它在HotSpot裡也不算full GC(從GC日誌可以看到這種GC沒有Full GC字樣)。那它如何處理young gen呢?其實整個都掃描了,只是掃描了young之後不收集它而已。

CMS的GC日誌可以是這樣的:

[GC [1 CMS-initial-mark: 179599K(356352K)] 182953K(507520K), 0.0086881 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.326/0.326 secs] [Times: user=0.33 sys=0.02, real=0.33 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.002/0.002 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.746/5.066 secs] [Times: user=1.03 sys=0.06, real=5.07 secs] 
[GC [Rescan (parallel) , 0.0384077 secs]530.572: [weak refs processing, 0.0010392 secs] [1 CMS-remark: 179599K(356352K)] 211693K(507520K), 0.0395753 secs] [Times: user=0.06 sys=0.00, real=0.04 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.093/0.093 secs] [Times: user=0.13 sys=0.00, real=0.09 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.008/0.008 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

以上日誌體現了CMS的各階段,其中兩個階段是Stop-The-World的,CMS-initial-markCMS-remark,它們對應的日誌就有[GC開頭,而併發階段的階段就沒有。

It is possible to configure the HotSpot VM to not garbage collect the young generation space on a full garbage collection prior to garbage collecting the old generation space using the command line option -XX:-ScavengeBeforeFullGC. The "-" character preceding the ScavengeBeforeFullGC disables the garbage collection of the young generation space on a full garbage collection. In contrast, a "+" character in front of ScavengeBeforeFullGC enables the garbage collection of the young generation space on a full garbage collection.

ScavengeBeforeFullGC引數能幹嘛呢?這個只用於ParallelScavenge GC上,在HotSpot的其他GC上不起任何作用。當PS決定做Full GC時,它會先觸發一次PSScavenge(PS系的Minor GC),然後再做真正的Full GC(PSMarkSweep或者PSCompact)。但必須注意的是這個Minor GC與Full GC仍然是相互獨立的兩次GC,前者仍然只收集young gen,而後者仍然收集包括young在內的全堆。這種配置下,先做一次minor GC的好處是把Full GC原本要做的一部分事情分擔到了一次獨立的Minor GC裡,而Java執行緒有可能在這個minor GC與Full GC之間的間隙裡稍微執行一小下,使得最大暫停時間變短了。(雖然總體看來暫停時間變長了,但兩次暫停並不是無縫隙連在一起的。)

可以參考我在這回帖裡寫的:http://hllvm.group.iteye.com/group/topic/35871#post-240188 所以說可以用-XX:-ScavengeBeforeFullGC讓Full GC時在收集old gen前不收集young gen完全是錯的。

Garbage collecting the young generation space prior to garbage collecting the old generation space usually results in less work for the garbage collector and more objects being garbage collected since objects in the old generation space may be holding object references to objects in the young generation space. If the young generation space is not garbage collected, any object in old generation space that holds a reference to an object in young generation space cannot be garbage collected.

如果有一種GC只收集old/perm gen的話,那麼young gen(至少其中一部分)必須是這種GC的根集合的一部分;在這種GC前先做一次minor GC收集掉young gen裡的垃圾會有助於減小前者的根集合,進而減少它要處理的物件的數量,於是就縮短了這種GC需要工作的時間。

HotSpot VM裡只有CMS的併發收集是符合上述描述的GC。倒真是有一個-XX:+CMSScavengeBeforeRemark的引數可設,作用正好就是在做CMS remark之前先強行觸發一次minor GC(這裡通常是ParNew)。

所幸這些細節錯誤在中文翻譯版裡應該都處理了,對應中文版78~79頁。

相關文章