簡單理解耗時的Java GC

參考地址:

http://www.cloudera.com/blog/2011/02/avoiding-full-gcs-in-hbase-with-memstore-local-allocation-buffers-part-1/

java GC工作在分代的模型上。大多數的對象要麼很快的死亡,要麼持續較長的時間。例如,方法棧中創建的對象只會持續幾毫秒時間,而緩存中的對象會持續幾分鐘。

既然對象有兩種不同的什麼週期,直覺認爲,使用不同的垃圾收集算法也許能夠在不同的週期中更好的完成工作。所以,JVM把堆空間拆封爲兩個不同的區域,新生代,舊生代。當對象剛開始創建的時候,會被分配到新生代。當對象在新生代中經過多次gc後,依然存活時,就假設這個對象將持續較長時間,就把這個對象複製到舊生代中去。

JVM加上啓動參數-XX:+UseParNewGC 和 -XX:+UseConcMarkSweepGC將爲新生代啓用Parallel New collector爲舊生代啓用Concurrent Mark-Sweep collector。

Parallel New collector工作時,會首先停止所有運行中的java線程。然後追蹤對象引用以判斷哪些對象是活着的。最後把活着的對象複製到一個一塊空閒的堆區域,更所所有的指針到新的地址。有兩個重要地方值得注意

  1. 它會暫停所有線程,但時間非常短暫。因爲新生代通常非常的小,而且是多線程收集的。它能夠非常快的完成工作。生產環境中,通常建議新生代設置不超過512M,這樣最壞的暫停時間也不會超過幾百毫秒。
  2. 收集時,複製所有存活的對象到一塊連續的空閒堆空間中去,這塊空間叫做S1,S2,交替使用。如果S1或者S2空間不夠使,剩下的存活對象會被複制到舊生代中區。

每次複製一個對象後,將增加這個對象的收集計數,當一個對象在新生代中被複制了一定次數後,該算法即判定該對象是長週期的對象,把他移動到舊生代。這個閾值叫着tenuring threshold。

舊生代的Concurrent Mark Sweep(CMS)

每次Parallel New collector工作時,會把一部分對象複製到舊生代中。所以,舊生代很快就會被填滿,就需要一個策略來收集它。CMS負責處理舊生代中的死亡對象。

CMS通過多個步驟來完成工作。有一些步驟會暫停所有線程(stop the world),其他步驟是和應用並行的執行的。主要的步驟爲:

  1. 初始標記(stop the world)。在這個步驟中,CMS放置一個標記在所有root對象中。一個root對象是被當前線程直接引用的對象,例如,線程當前使用的本地變量。這個步驟會很快完成,因爲root對象是非常少的。
  2. 並行標記。收集器開始跟隨並標記所有root對象的引用,直到標記完所有的引用,即存活的對象。
  3. 再次標記(stop the world)。在並行標記的過程中,有一些對象引用可以已經變化了,也有一些新的對象被創建。需要把這些變化考慮在內。這個步驟也非常的短暫,因爲CMS使用一個特殊的數據結構,只處理這期間的修改
  4. 並行回收。所有沒有被標記的對象,都將被刪除,釋放內存空間。在這個步驟中新創建的對象,會被直接標記,以免被刪除。

有幾個重要地方需要注意:

  1. stop the world的時間,是短暫的。所有掃描整個heap和刪除的操作都是並行執行的。
  2. CMS收集器,不會移動壓縮存活對象。所以空閒空間是分散在整個heap中的,會存在很多的碎片。

如上面描述,CMS收集器工作得很好,只暫停很短的時間,大多數耗時操作都是並行執行的。但運行中幾分鐘的長時間暫停是怎麼產生的?是因爲,CMS有兩個特殊的失敗情況需要處理。

第一種失敗,Concurrent Mode Failure,經常討論的一種失敗。假如我們有個8GB的堆空間,其中已使用了7GB。CMS將開始它的第一步操作。在這個時候,更多的對象被創建並轉移到舊生代中,如果轉移速度太快,舊生代將在CMS標記完所有對象前被填滿。因爲沒有更多的空間可供使用,應用程序將無法繼續執行。CMS不得不暫停並行標記,並暫停所有的線程,使用單線程的複製算法,把所有堆空間的存活對象複製到堆的開始,然後回收內存空間,在這個長時間的暫停後,程序才能繼續執行。這種情況看起來應該很容易避免,我們只需要讓CMS在內存空間不夠前完成工作。可以通過參數-XX:CMSInitiatingOccupancyFraction=N,來配置CMS開始工作的時機,N已使用的堆空間百分比。

第二種失敗是因爲空間碎片。CMS不會重新分配存活的對象,所以堆中的空閒空間將是分散的。例如,我們分配100W的對象,每個對象佔用空間1KB,消耗了所有的堆空間,然後回收所有的偶數對象,剩下50W的對象,佔用500M內存空間,還剩餘500M內存空間,但是剩餘空間是50W個1KB的不連續的快,如果這個時候,需要分配一個2KB的對象,將沒有可用的空間供其分配。在這種情況下,CMS也不得不暫停所有線程,壓縮堆空間。

所以,耗時的gc時間,主要是因爲CMS兩者異常工作方式產生的。應用程序設計中就需要考慮如何避免這兩情況的產生,從而減少full gc暫停對應用產生的致命影響


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章