Compaction phase
當清掃完畢以後,就開始執行compaction。Java的compaction是相當複雜的。因爲移動一個對象,必須修改他們所有的reference。而且如果一個reference是來自一個stack,並且我們不能確定它是否指向一個真實的object,可能它僅僅是一個float,那麼這些object
就不能被移動。一個對象是否可以被移動設計到它的”dosed”位是被置位。同樣pinned
object,那些被JNI引用的對象,只有到Jni
unnpined的時候才能被移動。Pinned object的可否移動的判斷更加複雜。主要依賴於mptr低三位標示它是否被清掃掉。標示被清掃的
的位存在兩個地方:1. The size + flags
字段,如果被標記到OLINK_IsSwapped
. 2. mptr
被標記到GC_FirstSwapped
。因此看來Java把int
這種普通類型和Object分開處理在GC中會造成過多的不能移動的對象和過多的碎片。對於GC來說很不明智,而且在其他地方也看不出有什麼必要分開處理。
否則幹嗎還要做一個Integer類呢?而C#在這點上來說優勢更大。
IBM java中的Compaction算法爲了避免過多的移動對象和利用移動處理一些沒有被收集的空閒塊因而出奇的複雜。他採用了一種和hotspot不同的算法。Sam Borman舉了一個很形象的例子,把整個Heap想象成一個倉庫,倉庫堆放了不同尺寸的傢俱。由於出庫的原因,傢俱之間存在着一定的空隙。Compaction的工作就是把傢俱往一個方向推來清理空隙。把靠近牆的傢俱推倒牆邊,然後讓第二個傢俱與第一件緊靠在一起。以此類推,然後所有得傢俱靠再一起,而空隙在另外一邊。Pinned and dosed objects 不能被移動的情況會複雜化這個算法,但是主要思想不變。
緊縮規避(Compaction avoidance)
Compaction avoidance的主要目的在於開闢較大內存的時候降低Compaction的使用次數來保證GC pause time能夠足夠短。在Ibm jvm中的Compaction的執行條件如下:
1. 如果開闢一個大內存的時候遍例Free list發現沒有合適的free storge激發alloc failure時間
2. 在上次GC過程中出現了一次alloc failure
3. 被激活的Heap(heap limited到heap base之間的heap)只有5%爲free
4. 被激活的Heap不大於128K
IBM jvm在上面四個條件中滿足一項就執行compaction。其中最爲常見的是第一種,
爲了避免Companction,Ibmjava採用了緊縮規避的方法。這個方法稱爲荒野內存(wilderness preservation),也就是在heap limit之上再開闢一塊內存。這塊內存保持原始狀態,其大小爲激活Heap的5%,默認設置爲3M.如果一旦有一個大塊內存需要開闢,而freelist中沒有合適的storge的時候就使用wilderness preservation保證不拋出 alloc failure。一旦wilderness被用盡則產生一個alloc failure通知GC執行Compaction。通常來說wilderness preservation能夠保證不使用Compaction,因爲基本上使用到wilderness的對象是這個應用程序中最大的對象。
Ibm的JVM關鍵實現就是這樣,我們可以看到ibmJVM使用的很多算法讓我們原本考慮的一些gc的困難降低到了一個可以忍受的限度,比如STW的pause time,其實只涉及了sweep和compaction,mark phase在程序運行的同時就完成了基本不影響程序的正常工作。而且由於使用了bitsweep,和緊縮規避使得STW的時間大大降低,他們兩個的工作量的總和不到Mark的30%。而且在多對稱處理器上又採用並行mark和sweep,可以近一部的提高GC效率。好了用了兩個小時寫這篇文章真的很累,我把Java的VM的實現細節公佈出來,大家談論就”有的放肆”了吧。我也是匆匆看了以後結合以前經驗寫出來得,大家看了以後有什麼意見儘管提。我不能回答的,寫信去問Sam borman。