Garbage First

原文 http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html
本文摘自《構建高性能的大型分佈式Java應用》一書,Garbage First簡稱G1,它的目標是要做到儘量減少GC所導致的應用暫停的時間,讓應用達到準實時的效果,同時保持JVM堆空間的利用率,將作爲CMS的替代者在JDK 7中閃亮登場,其最大的特色在於允許指定在某個時間段內GC所導致的應用暫停的時間最大爲多少,例如在100秒內最多允許GC導致的應用暫停時間爲1秒,這個特性對於準實時響應的系統而言非常的吸引人,這樣就再也不用擔心繫統突然會暫停個兩三秒了。

G1要做到這樣的效果,也是有前提的,一方面是硬件環境的要求,必須是多核的CPU以及較大的內存(從規範來看,512M以上就滿足條件了),另外一方面是需要接受吞吐量的稍微降低,對於實時性要求高的系統而言,這點應該是可以接受的。

爲了能夠達到這樣的效果,G1在原有的各種GC策略上進行了吸收和改進,在G1中可以看到增量收集器和CMS的影子,但它不僅僅是吸收原有GC策略的優點,並在此基礎上做出了很多的改進,簡單來說,G1吸收了增量GC以及CMS的精髓,將整個jvm Heap劃分爲多個固定大小的region,掃描時採用Snapshot-at-the-beginning的併發marking算法(具體在後面內容詳細解釋)對整個heap中的region進行mark,回收時根據region中活躍對象的bytes進行排序,首先回收活躍對象bytes小以及回收耗時短(預估出來的時間)的region,回收的方法爲將此region中的活躍對象複製到另外的region中,根據指定的GC所能佔用的時間來估算能回收多少region,這點和以前版本的Full GC時得處理整個heap非常不同,這樣就做到了能夠儘量短時間的暫停應用,又能回收內存,由於這種策略在回收時首先回收的是垃圾對象所佔空間最多的region,因此稱爲Garbage First。

看完上面對於G1策略的簡短描述,並不能清楚的掌握G1,在繼續詳細看G1的步驟之前,必須先明白G1對於JVM Heap的改造,這些對於習慣了劃分爲new generation、old generation的大家來說都有不少的新意。

G1將Heap劃分爲多個固定大小的region,這也是G1能夠實現控制GC導致的應用暫停時間的前提,region之間的對象引用通過remembered set來維護,每個region都有一個remembered set,remembered set中包含了引用當前region中對象的region的對象的pointer,由於同時應用也會造成這些region中對象的引用關係不斷的發生改變,G1採用了Card Table來用於應用通知region修改remembered sets,Card Table由多個512字節的Card構成,這些Card在Card Table中以1個字節來標識,每個應用的線程都有一個關聯的remembered set log,用於緩存和順序化線程運行時造成的對於card的修改,另外,還有一個全局的filled RS buffers,當應用線程執行時修改了card後,如果造成的改變僅爲同一region中的對象之間的關聯,則不記錄remembered set log,如造成的改變爲跨region中的對象的關聯,則記錄到線程的remembered set log,如線程的remembered set log滿了,則放入全局的filled RS buffers中,線程自身則重新創建一個新的remembered set log,remembered set本身也是一個由一堆cards構成的哈希表。

儘管G1將Heap劃分爲了多個region,但其默認採用的仍然是分代的方式,只是僅簡單的劃分爲了年輕代(young)和非年輕代,這也是由於G1仍然堅信大多數新創建的對象都是不需要長的生命週期的,對於應用新創建的對象,G1將其放入標識爲young的region中,對於這些region,並不記錄remembered set logs,掃描時只需掃描活躍的對象,G1在分代的方式上還可更細的劃分爲:fully young或partially young,fully young方式暫停的時候僅處理young regions,partially同樣處理所有的young regions,但它還會根據允許的GC的暫停時間來決定是否要加入其他的非young regions,G1是運行到fully-young方式還是partially young方式,外部是不能決定的,在啓動時,G1採用的爲fully-young方式,當G1完成一次Concurrent Marking後,則切換爲partially young方式,隨後G1跟蹤每次回收的效率,如果回收fully-young中的regions已經可以滿足內存需要的話,那麼就切換回fully young方式,但當heap size的大小接近滿的情況下,G1會切換到partially young方式,以保證能提供足夠的內存空間給應用使用。

除了分代方式的劃分外,G1還支持另外一種pure G1的方式,也就是不進行代的劃分,pure方式和分代方式的具體不同在下面的具體執行步驟中進行描述。

掌握了這些概念後,繼續來看G1的具體執行步驟:

1. Initial Marking

G1對於每個region都保存了兩個標識用的bitmap,一個爲previous marking bitmap,一個爲next marking bitmap,bitmap中包含了一個bit的地址信息來指向對象的起始點。

開始Initial Marking之前,首先併發的清空next marking bitmap,然後停止所有應用線程,並掃描標識出每個region中root可直接訪問到的對象,將region中top的值放入next top at mark start(TAMS)中,之後恢復所有應用線程。

觸發這個步驟執行的條件爲:

l G1定義了一個JVM Heap大小的百分比的閥值,稱爲h,另外還有一個H,H的值爲(1-h)*Heap Size,目前這個h的值是固定的,後續G1也許會將其改爲動態的,根據jvm的運行情況來動態的調整,在分代方式下,G1還定義了一個u以及soft limit,soft limit的值爲H-u*Heap Size,當Heap中使用的內存超過了soft limit值時,就會在一次clean up執行完畢後在應用允許的GC暫停時間範圍內儘快的執行此步驟;

l 在pure方式下,G1將marking與clean up組成一個環,以便clean up能充分的使用marking的信息,當clean up開始回收時,首先回收能夠帶來最多內存空間的regions,當經過多次的clean up,回收到沒多少空間的regions時,G1重新初始化一個新的marking與clean up構成的環。

2. Concurrent Marking

按照之前Initial Marking掃描到的對象進行遍歷,以識別這些對象的下層對象的活躍狀態,對於在此期間應用線程併發修改的對象的以來關係則記錄到remembered set logs中,新創建的對象則放入比top值更高的地址區間中,這些新創建的對象默認狀態即爲活躍的,同時修改top值。

3. Final Marking Pause

當應用線程的remembered set logs未滿時,是不會放入filled RS buffers中的,在這樣的情況下,這些remebered set logs中記錄的card的修改就會被更新了,因此需要這一步,這一步要做的就是把應用線程中存在的remembered set logs的內容進行處理,並相應的修改remembered sets,這一步需要暫停應用,並行的運行。

4. Live Data Counting and Cleanup

值得注意的是,在G1中,並不是說Final Marking Pause執行完了,就肯定執行Cleanup這步的,由於這步需要暫停應用,G1爲了能夠達到準實時的要求,需要根據用戶指定的最大的GC造成的暫停時間來合理的規劃什麼時候執行Cleanup,另外還有幾種情況也是會觸發這個步驟的執行的:

l G1採用的是複製方法來進行收集,必須保證每次的”to space”的空間都是夠的,因此G1採取的策略是當已經使用的內存空間達到了H時,就執行Cleanup這個步驟;

l 對於full-young和partially-young的分代模式的G1而言,則還有情況會觸發Cleanup的執行,full-young模式下,G1根據應用可接受的暫停時間、回收young regions需要消耗的時間來估算出一個yound regions的數量值,當JVM中分配對象的young regions的數量達到此值時,Cleanup就會執行;partially-young模式下,則會盡量頻繁的在應用可接受的暫停時間範圍內執行Cleanup,並最大限度的去執行non-young regions的Cleanup。

這一步中GC線程並行的掃描所有region,計算每個region中低於next TAMS值中marked data的大小,然後根據應用所期望的GC的短延時以及G1對於region回收所需的耗時的預估,排序region,將其中活躍的對象複製到其他region中。


G1爲了能夠儘量的做到準實時的響應,例如估算暫停時間的算法、對於經常被引用的對象的特殊處理等,G1爲了能夠讓GC既能夠充分的回收內存,又能夠儘量少的導致應用的暫停,可謂費盡心思,從G1的論文中的性能評測來看效果也是不錯的,不過如果G1能允許開發人員在編寫代碼時指定哪些對象是不用mark的就更完美了,這對於有巨大緩存的應用而言,會有很大的幫助,G1將隨JDK 6 Update 14 beta發佈
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章