1. 程式人生 > >通過圖文給你講明白java GC的實現原理

通過圖文給你講明白java GC的實現原理

本文原連結
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

1. JAVA GC 概述

JAVA GC採用了分代思想,將java堆分成新生代,年老代,永久代。GC演算法主要有標記-清除,標記-壓縮,複製演算法。

  • 新生代:新生代被分成三個部分 eden區和2個survivor區(from和to兩個分割槽)。當建立物件,需要jvm分配記憶體時,會在新生代的eden區尋找合適的記憶體區域。如果當eden區記憶體不夠時,會觸發minor GC。eden區存活物件和from區的存活物件將會被複制到to區。當to區的物件年齡超過了晉升的年齡設定,物件將被提升到老年代。新生代GC用的是複製演算法
  • 年老代: 年老代裡存放的都是存活時間較久的,大小較大的物件,因此年老代使用標記整理演算法。當年老代容量滿的時候,會觸發一次Major GC(full GC),回收年老代和年輕代中不再被使用的物件資源。年老代演算法用的是標記-清除
  • 永久代:指記憶體的永久儲存區域,主要存放Class和Meta(元資料)的資訊。Class在被載入的時候被放入永久區域。它和和存放例項的區域不同,GC不會在主程式執行期對永久區域進行清理。所以這也導致了永久代的區域會隨著載入的Class的增多而脹滿,最終丟擲OOM異常。

2. 圖文描述標記-清除和標記-壓縮

Step 1:標記(Marking)

GC的第一步叫做標記。在這個步驟GC通過遍歷記憶體區辨別哪些記憶體在使用,哪些內容沒有使用。並做好標記

image

如上圖藍色的表示存活的物件,金黃色表示垃圾物件。在標記階段,需要掃描整個該記憶體區的物件,並標記。這個過程可能會比較耗時

Step 2: 清除(Normal Deletion)

清除階段移除掉垃圾物件,並且用一個連結串列維護空閒的區域

image

記憶體分配器持有空閒記憶體區的引用,以便分配記憶體給新的物件

Step 2a: 壓縮(Deletion with Compacting)
為了提升效能,在Step 2的基礎上,在刪除完垃圾物件後。我們可以把存活的物件移動到記憶體區的頭部。這樣下次分配記憶體的時候會更快。主要原因是標記-清除會造成比較大的記憶體碎片,每當需要分配記憶體時,都需要遍歷空閒連結串列。而壓縮演算法,會把記憶體碎片整理成一個大的完整記憶體塊。

image

3. 分代垃圾回收

為什麼要採用分代垃圾回收?

正如前面所說,標記和壓縮物件,對java虛擬機器而言會比較耗時。當java虛擬機器分配了越來越多的物件後。GC所花費的時間將會更長。然而根據經驗分析,絕大多數的物件存活時間都比較短。這樣我們可以把存活長的物件和存活短的物件隔離開。這樣GC會更加高效

image

JVM分代
將jvm堆記憶體分割成更小的記憶體區,會提高jvm的gc效能。堆被分成 (新生代)Young Generation,(年老代)Old or Tenured Generation, and (永久代)Permanent Generation

image

  • 所有的物件都會在新生代中分配記憶體。當新生代記憶體不夠的時候。將會出發minor GC。如果新生代中的物件存活時間都很短,呢麼minor GC的效率將會很高。如果新生代裡面充滿了垃圾物件,那麼回收速度將會很快(因為標記的時間短了)。一些存活下來的物件年齡將會增加,並且最終會被移動到年老代
  • Stop the World Event:所有的minor GC都是”Stop the World”事件。這意味著出來GC執行緒,程式的其他執行緒對將暫停知道GC完成。Minor GC都是Stop the World Event
  • 年老代是用來儲存長存活時間的物件。典型的我們可以給物件設定一個年齡界限,當新生代的物件存活年齡超過這個界限,物件將會從新生代移動到年老代。當年老代的記憶體不夠的時候,將會出發Major GC。Major GC也是Stop the World Event。通常來說Major GC比Minor GC更慢,因為Major GC回收的是整個新生代和年老代的所有垃圾物件。因此對於響應性高的程式,應該儘量減少Major GC。需要注意的是Stop the World Event的時間受到在年老代中使用的垃圾回收器的影響,不受新生代的影響
  • 永久代包含了JVM的元資料。包括類資訊,方法資訊等。永久代由JVM在應用執行期生成。另外 javase的類庫中的類資訊也可能存放在這裡

分代垃圾回收的處理過程

現在你明白為什麼堆分成不同的世代,現在是時候看看這些空間是如何相互作用的。 下面的圖片將介紹JVM中的物件分配和老化過程。

1. 一開始,任何新的物件都會在eden空間分配記憶體,兩個survivor空間一開始都是空的

image

2. 當eden空間被填滿了,minor GC將被觸發

image
S0 survivor區物件裡的 1 3表示物件的年齡

3. Eden空間的存活的物件將被複制到第一個survivor空間,年齡+1,垃圾物件將會被清除掉
image

4. 下一次minor GC發生時,Eden空間的存活物件將被複制到空閒的survivor空間S1(年齡+1),另外在前一次minor GC S0空間的存活物件也會被複制到S1(年齡+1),垃圾物件會被清除掉

image

5. 下一次minor GC發生時,還是重複第4條的內容,只是兩個survivor空間對調了,這次是從S1複製到S0空間
image

6. 新生代晉升到年老代。當minor GC發生時,如果survivor空間中的物件年齡超過了晉升的年齡限定,物件會被複制到年老代
image

7. 當minor GC不斷觸發,將會有物件不斷被晉升到年老代
image

8. 上面的圖文完美的解釋了minor GC的處理過程。最終,當年老代的記憶體被填滿的時候,將會觸發major GC。Major GC在年老代用的是標記壓縮演算法。同時新生代的物件將被清除
image

4.後續

後面我將計劃寫一系列關於垃圾回收的博文。主要內容會涉及到垃圾回收的演算法描述。