JVM full GC的奇怪現象,求解惑?

今天本來是想用JMX寫個監控程序,但在嘗試監控GC情況時,發現一個奇怪的現象

能看出來新生代用PS Scavenge,老年代用PS MarkSweep
我用JMX分別取了兩個回收器的上次回收情況,發現一個奇怪的問題:

PS MarkSweep上一次垃圾回收前後的內存使用情況是:
[PS Survivor Space] - before:327680 after:0
[PS Eden Space] - before:0 after:0
[PS Old Gen] - before:203895944 after:203923920
[PS Perm Gen] - before:211102776 after:211102776

這次垃圾回收前後Old Gen的內存使用情況幾乎沒有變化,都是194M左右,回收後甚至比回收前還高了一點點,而Old Gen的最大內存是910M。Perm Gen的內存使用率也沒有變化(Perm區分了560M)。只有Survivor Space從327680降到了0。
這次Full GC看起來不僅基本啥事都沒幹,而且似乎根本不應該被觸發?觸發前Eden是空的,Old Gen和Perm Gen的可用空間都還很大。
求解惑。

另外附加一些信息:
本次PS MarkSweep的耗時是1743ms,GC開始時間和在上一次PS Scavenge的GC完成時間完全一樣,也就是說,上一次YoungGC完成後立刻就開始了這次FullGC,並且基本啥都沒回收掉。
JVM版本是64-bit Hotspot VM, 1.6.0_27


不奇怪,一切現象都是有原因的。

先來了解些背景信息。在題主所使用的JDK 6 update 27的HotSpot VM里,-XX:+UseParallelGC會啟用題主所說的配置(這也是HotSpot VM在Server Class Machine上的默認配置)。

其中,負責執行minor GC(只收集young gen)的是PS Scavenge,全稱是ParallelScavenge,是個並行的copying演算法的收集器;
而負責執行full GC(收集整個GC堆,包括young gen、old gen、perm gen)的是PS MarkSweep——但整個收集器並不是並行的,而在骨子裡是跟Serial Old是同一份代碼,是串列收集的。其演算法是經典的LISP2演算法,是一種mark-compact GC(不要被MarkSweep的名字騙了)。
(注意這個跟使用-XX:+UseParallelOldGC所指定的並不是同一個收集器,那個是PS Compact,是個並行的全堆收集器)

ParallelScavenge這個GC套裝的full GC有個很特別的實現細節,那就是:當觸發full GC的時候,實際上會先使用PS Scavenge執行一次young GC收集young gen,然後緊接著去用PS MarkSweep執行一次真正的full GC收集全堆。
所以說題主看到的現象就是很正常的一次ParallelScavenge的full GC而已。

要控制這個行為,可以使用VM參數:

product(bool, ScavengeBeforeFullGC, true,
"Scavenge youngest generation before each full GC, "
"used with UseParallelGC")

指定 -XX:-ScavengeBeforeFullGC 就可以不在執行full GC的時候先執行一次PS Scavenge。

jdk6/jdk6/hotspot: 7561dfbeeee5 src/share/vm/gc_implementation/parallelScavenge/psMarkSweep.cpp

void PSMarkSweep::invoke(bool maximum_heap_compaction) {
// ...

if (ScavengeBeforeFullGC) {
PSScavenge::invoke_no_policy();
}

// ...
}

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

題主說:

這次FullGC為什麼會被觸發?回收前Old Gen的使用率是194M/910M,Survivor只佔用了300多K,Eden則是0,此時Old Gen空間很富餘,從YoungGen晉陞的對象也只有300多K,PermGen也很富餘……按照我的理解,似乎此時不應該觸發FullGC啊?

正因為上面說的,ParallelScavenge這套GC在觸發full GC時實際上會先做一個young GC(PS Scavenge),再執行真正的full GC(PS MarkSweep),所以題主在看數據的時候就被弄暈了:
題主實際看到的是在「真正的full GC」的數據,而這是在剛剛做完那個young GC後的,所以自然,此時edgen是空的,而survivor space里的對象都是活的。

要看這次full GC為何觸發,必須去看這個因為full GC而觸發的young GC之前的狀態才行。

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

另外,做完full GC後old gen的使用量上升也是非常正常的行為。HotSpot的full GC實現中,默認young gen里所有活的對象都要晉陞到old gen,實在晉陞不了才會留在young gen。假如做full GC的時候,old gen里的對象幾乎沒有死掉的,而young gen又要晉陞活對象上來,那麼full GC結束後old gen的使用量自然就上升了。


推薦閱讀:

TAG:Java | Java虛擬機JVM | GC垃圾回收計算機科學 | HotSpotVM |