1. 程式人生 > 實用技巧 >5.垃圾回收器

5.垃圾回收器

1.如何判斷垃圾物件 可達性分析演算法 也叫根搜尋演算法,Java語言採用這種分析演算法去判斷垃圾物件。 可作GCRoot的物件 虛擬機器棧(棧幀中的本地變量表)中的引用的物件。 方法區中的類靜態屬性引用的物件。 方法區中的常量引用的物件。 本地方法棧中JNl(即一般說的Native方法)的引用的物件。 2.常見的垃圾回收演算法 2.1複製回收演算法(Mark-Sweep) 來回收新生代 Hotspot的新生代: Eden Survivor(from) Survivor(to) 正常物件記憶體分配的時候,只會使用Eden區和Survivor其中的一塊區域。 使用複製演算法的垃圾回收步驟:
1. 當Eden區發生垃圾回收之後,會將Eden區和Survivor其中的一塊區域中的物件,複製到另一塊Survivor區域 2. 然後將將Eden區和Survivor其中的一塊區域中的物件完全清理掉。 缺點:記憶體分配時會浪費新生代的10%的空間 2.2.標記清除演算法(Mark-Sweep) 最基本的演算法,主要分為標記和清除2個階段。首先標記出所有需要回收的物件,在標記完成後統一回收掉所有被標記的物件 缺點:1. 效率不高。2. 產生空間碎片。會產生大量不連續的記憶體碎片,會導致大物件可能無法分配,提前觸發GC 。 2.3標記整理演算法(Mark-Compact) 標記過程仍然與“標記-清除”演算法一樣,然後讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。
新生代,每次垃圾回收都有大量物件失去,選擇複製演算法 老年代,物件存活率高,無人進行分配擔保,就必須採用標記清除或者標記整理演算法。 3.物件的分配 一個物件產生之後 首先判斷能不能在棧上分配(逃逸分析或者是大的物件),如果棧上不能分配 進行執行緒本地分配(佔用eden 預設1%,這塊空間是執行緒私有的,避免多執行緒爭搶。) 如果分配不了,在Eden分配,經過15次GC還存活(cms 6次)進入Old區,若在GC的時候,會將存活的物件copy到S0區,若超過S區的一半的記憶體時 把GC年紀最大的直接放到Old區。 4.垃圾回收器 常用的垃圾回收器: Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1、ZGC

常用組合:

1.Serial和Serial Old Serial 使用單執行緒進行垃圾回收,並且會停止所有工作執行緒(STW),停頓時間長,故現在用的很少(用於回收新生代) 。Serial Old 用於老年代,使用標記清理演算法,也是單執行緒。

2.Parallel Scavenge和 Parallel Old

若JVM沒有做任何調優,就是預設的這一組。PS 使用多執行緒清理垃圾,用於新生代。 PO使用整理演算法。 3.ParNew和CMS

ParNew 跟PS沒有區別

CMS: 1.基於"標記-清除"演算法(不進行壓縮操作,產生記憶體碎片); 2.以獲取最短回收停頓時間為目標; 3.併發收集、低停頓; 是HotSpot在JDK1.5推出的第一款真正意義上的併發(Concurrent)收集器;第一次實現了讓垃圾收集執行緒與使用者執行緒(基本上)同時工作; CMS產生的階段: 1.初始標記STW開始標記。 2.併發標記和應用程式同時執行。 3.重新標記STW,在併發標記中產生的新垃圾在重新標記. 4.併發清理。

CMS缺點:1.產生記憶體碎片. 2.產生浮動垃圾。在併發清理中還會產生垃圾。

4.G1

G1是一種服務端應用使用的垃圾回收器,目標是用在多核,大記憶體的機器上,它在大多數情況上可以實現指定的GC暫停世界,同時還能保持較高的吞吐量.

G1將記憶體劃分成了多個大小相等的Region(預設是512K),Region邏輯上連續,實體記憶體地址不連續。同時每個Region被標記成E、S、O、H,分別表示Eden、Survivor、Old、Humongous。其中E、S屬於年輕代,O與H屬於老年代。

H表示Humongous。從字面上就可以理解表示大的物件(下面簡稱H物件)。 當分配的物件大於等於Region大小的一半的時候就會被認為是巨型物件。H物件預設分配在老年代,可以防止GC的時候大物件的記憶體拷貝。通過如果發現堆記憶體容不下H物件的時候,會觸發一次GC操作。 在進行Young GC的時候,Young區的物件可能還存在Old區的引用, 這就是跨代引用的問題。 為了解決Young GC的時候掃描整個老年代,G1引入了Card TableRemember Set的概念,基本思想就是用空間換時間。這兩個資料結構是專門用來處理Old區到Young區的引用。Young區到Old區的引用則不需要單獨處理,因為Young區中的物件本身變化比較大,沒必要浪費空間去記錄下來。 RSet:全稱Remembered Sets, 用來記錄外部指向本Region的所有引用,每個Region維護一個RSet。RSet的價值在於使得垃圾回收器不需要掃描整個堆 找到誰引用了當前分割槽中的物件,只需要掃描RSet即可. Card Table:如果Old區中的物件指向了Young區,就將它設為Dirty(髒的),下次掃描時,只需要掃描Dirty Card,在結果上Card Table使用了Bitmap. 4.1 G1 GC主要可以分為兩個階段

4.1.1 全域性併發標記(global concurrent marking) 全域性併發標記又可以進一步細分成下面幾個步驟:

  • 初始標記(initial mark,STW)。它標記了從GC Root開始直接可達的物件。初始標記階段借用young GC的暫停,因而沒有額外的、單獨的暫停階段。
  • 併發標記(Concurrent Marking)。這個階段從GC Root開始對heap中的物件標記,標記執行緒與應用程式執行緒並行執行,並且收集各個Region的存活物件資訊。過程中還會掃描上文中提到的SATB write barrier所記錄下的引用。
  • 最終標記(Remark,STW)。標記那些在併發標記階段發生變化的物件,將被回收。
  • 清除垃圾(Cleanup,部分STW)。這個階段如果發現完全沒有活物件的region就會將其整體回收到可分配region列表中。 清除空Region。
4.1.2 拷貝存活物件(Evacuation) Evacuation階段是全暫停的。它負責把一部分region裡的活物件拷貝到空region裡去(並行拷貝),然後回收原本的region的空間。 Evacuation階段可以自由選擇任意多個region來獨立收集構成收集集合(collection set,簡稱CSet),CSet集合中Region的選定依賴於上文中提到的停頓預測模型,該階段並不evacuate所有有活物件的region, 只選擇收益高的少量region來evacuate,這種暫停的開銷就可以(在一定範圍內)可控。 4.2 G1在併發標記中的演算法 併發標記演算法:三色標記 把物件分為三種顏色:黑色(自身和成員表裡都已經標記),灰色(自身被標記,成員表裡未被標記),白色(未被標記的物件). 如下圖,比如一個物件A,它所引用的物件B和C都已經標記完成,自己就變成黑色.B物件還有一個引用指向白色物件沒有被標記這個時候B是灰色.

漏標:當黑色物件A指向白色物件D,灰色物件B指向白色沒了.如下圖,這樣會產生漏標,遍歷不到。必須具備兩個兩個條件:黑色指向白色物件,灰色指向白色物件的引用消失.

CMS和G1的核心就在於併發標記的執行緒和工作執行緒同時進行,只有這個階段會產生漏標.

兩種解決方案:

1.增量更新,關注引用的增加,把黑色A物件變成灰色,下次需要重新掃描屬性.(CMS使用).

2.STAB 關注引用的刪除。當B指向D的引用消失時,把這個引用推到GC的堆疊,保證D還能被掃描到,下次掃描直接能掃白色,不會產生漏標(G1使用).

當灰色到白色引用消失時,由於有RSet存在,不需要掃描整個堆去查詢指向白色的引用,效率比較高.