1. 程式人生 > >垃圾收集器與記憶體分配策略——垃圾收集演算法與HotSpot虛擬機器演算法實現

垃圾收集器與記憶體分配策略——垃圾收集演算法與HotSpot虛擬機器演算法實現

垃圾收集演算法的具體實現涉及大量的程式細節,這裡只描述其演算法的基本思想和發展過程

一、常見的垃圾收集演算法對比如下

收集演算法 具體實現 優點 不足
標記-清除演算法

1、首先標記出所有需要回收的物件

2、標記完成之後,統一回收所有被標記的物件

效率低:標記和清除兩個過程效率都不高

空間問題:標記清除之後又大量的碎片,空間碎片太對會導致以後需要分配大物件時無法找到連續的記憶體空間而觸發垃圾收集動作

複製演算法

1、將可用記憶體劃分成兩塊,每次只使用其中的一塊。

2、當這一塊記憶體用完了,就將還存活的物件複製到另外一塊記憶體上面,然後把使用過的記憶體空間一次性清空

相比標記-清除演算法的實現來說,實現簡單,執行高效

這種演算法的實現是將可用記憶體劃分,可用空間減小。

IBM公司的專門研究表明,89%的物件都是朝生夕死,所以並不需要按照1:1來劃分記憶體空間。HotSpot虛擬機器中,按照8:1:1的比例,劃分Eden空間和兩個Survivor空間

Survivor空間不足時,需要依賴其他記憶體(老年代)進行分配擔保
標記-整理演算法

1、標記過程跟標記-清除演算法一樣

2、讓可存活的物件向著一端移動,然後清理掉端邊界外的記憶體

適用於老年代

分代收集演算法

分代收集演算法沒有什麼新的思想,只是按照物件存活的週期不同,將記憶體劃分為幾塊,這樣就可以跟進每一塊年代的特點,選用最合適的收集演算法

二、HotSpot虛擬機器演算法實現

前面介紹了判斷物件存活的判定演算法和垃圾收集演算法,而在HotSpot虛擬機器上實現這些演算法時,必須對演算法的執行效率有嚴格的考量,才能保證虛擬機器高效執行。 

1、列舉根節點

從可達性分析對中從GC Roots節點找到引用鏈這個操作為例,可作為GC Roots的節點主要在全域性引用和執行上下文中。現在很多應用僅方法區就能達到數百兆,如果要逐個檢查這裡面的引用,那麼必然會消耗很多的時間。

另外,可達性分析對執行時間的銘感還體現在GC 停頓上,因為這項分析工作必須在一個能確保一致性的快照中進行。這裡的一致性是指整個分析期間整個執行系統看起來就像被凍結在某個時間點上一樣。不可以出現分析過程中物件引用關係還在變化的情況。這點是導致GC執行時必須停頓所有的Java執行執行緒(Sun公司將這件事稱為Stop The World)的原因,即使是號稱不會發生停頓的CMS收集器中,列舉根節點時也必須要進行停頓。

當執行系統停頓下來之後,虛擬機器並不需要一個不漏地檢查完所有的執行上線文和全域性變數的引用未知,虛擬機器應該有辦法得到哪些地方存放著物件引用。在HotSpot虛擬機器中,是使用一組稱為OopMap的資料結構來達到這個目的的。

2、安全點

在OopMap的協助下,HotSpot虛擬機器快速完成GC Roots列舉,但是一個很現實的問題隨之而來,可能導致引用關係發生變化,或者說能使OopMap內容變化的指令非常多。如果為每一個指令都生成對應的OopMap的話,那GC的成本將變得很高。實際上,HotSpot虛擬機器也的確沒有為每條指令都生成OopMap,只有在特定的位置記錄了這些資訊。這些位置就稱為安全點(Safepoint),也就是說程式並非在所有的笛梵都能停下來進行GC,只有在到達安全點時,才能進行暫停。

SafePoint的選定,既不能讓GC等待時間太長,也不能過於頻繁。所以安全點的選定基本上是以程式“是否具有讓程式長時間執行的特徵”為標準進行選定的——因為每條指令執行的時間都非常短暫,程式不可能因為指令流長度太長這個原因而過長時間執行。長時間執行的最顯著特徵就是指令序列複用。例如方法呼叫,迴圈跳轉,異常跳轉等地方,所以具備這些功能的指令才會產生SafePoint。

3、安全區域

使用SafePoint 似乎已經完美解決了程式進入GC的問題,但是,如果再不太長的時間內,就遇到可進入的GC的SafePoint,程式又不執行(例如程式沒有分配到CUP時間片,或者執行緒處於Sleep狀態),這時候執行緒無法響應JVM的中斷請求。對於這種情況,就需要安全區域(Safe Region)來解決

安全區域時指在一段程式碼片段中,引用關係不會發生變化,在這個區域中的任意地方開始GC都是可安全的,我們也可以把Safe Region 看做是擴充套件了的SafePoint。

三、垃圾收集器

垃圾收集演算法是方法論,垃圾收集器是具體實現。Java虛擬機器規範中對垃圾收集器如何實現並沒有任何規定,因此不同的廠商,不同版本的虛擬機器廠商提供的垃圾收集器都會有很大的差別。

在基於JDK1.7 Update 14 之後的HotSpot虛擬機器中,包含的所有收集器如下圖所示:

如上的7中垃圾收集器,用實現連線起來的代表可以搭配使用。

需要明確的一點是:

如上列出來的所有收集器,沒有說那個最好,因為直到現在為止,還沒有最好的收集器出現

1、Serial收集器

Serial收集器是最基本,也是發展最悠久的收集器,是一個單執行緒的收集器。他的單執行緒的意義並不僅僅說明它只會使用一個CPU或者一條收集執行緒完成垃圾回收,更重要的是在它進行垃圾回收時,必須暫停其他所有的工作執行緒。直到它完成垃圾回收

2、ParNew 收集器

ParNew 收集器是Serial收集器的多執行緒版本,除了使用多執行緒完成垃圾回收外,其餘行為包括Serial收集器可用的所有控制引數,收集演算法,Stop The World,物件分配規則,回收策略,等都與Serial收集器完全一樣。

ParNew收集器是許多執行在Server模式下虛擬機器的首選新生代收集器,其中一個重要的原因就是,除Serial收集器外,目前只有它能與CMS收集器配合使用。ParNew收集器是在開啟 -XX:+UseConcMarkSweepGC 選項後的預設新生代收集器,也可以通過制定 -XX:UseParNewGC 選項來強制使用它。

ParNew收集器在單CPU的環境下,絕對不會有比Serial收集器更好的效果。在多CPU的環境下,可以使用 -XX:+ParallelGCThreads 引數來限制垃圾收集器的執行緒數

3、Parallel Scavenge 收集器

Parallel Scavenge 收集器是新生代收集器,也是使用複製演算法,看上去與ParNew 收集器一樣,但是與其他收集器的關注點不同,CMS等收集器的關注點是儘可能縮短垃圾收集時間和使用者執行緒的停頓時間。而Parallel Scavenge 收集器的目標則是達到一個可控的吞吐量

吞吐量:執行使用者程式碼的時間/(執行使用者程式碼的時間 + 垃圾收集時間),如果虛擬機器總共執行100分鐘,垃圾收集花掉1分鐘,那吞吐量就
是99%

停頓時間越短,就越適合與使用者互動的程式,良好的響應速度能提升使用者體驗,而高吞吐量則可以高效利用CPU時間,儘快完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。

Parallel Scavenge收集器提供了兩個引數用於精確控制吞吐量,分別控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis引數),以及直接設定吞吐量大小的-XX:GCTimeRatio引數。

-XX:MaxGCPauseMillis 是一個大於0的毫秒數,但是不能認為將該值設定的小一點就可以使系統的垃圾收集速度變快,縮短GC停頓時間是以犧牲吞吐量和新生代空間來換取的(例如:將新生代的空間從500M調整到300M,這樣看似收集起來會快一點,但是收集的頻率會增加,原來10秒收集一次,每次停頓100毫秒,現在可能5秒就需要收集一次,每次停頓70毫秒,停頓時間的確在降低,但是吞吐量也下降了)

-XX:GCTimeRatio 引數的值應該是一個大於0,且小於100的整數,也就是垃圾收集時間佔總時間的比率。

Parallel Scavenge 收集器有一個開關引數:-XX:+UseAdaptiveSizePolicy,當這個引數新增之後,就不需要手動指定新生代大小,Eden與Survivor的比例,晉升老年代物件的大小了等細節引數了,虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數也提供最合適的停頓時間或者最大的吞吐量。這種調節方式成為GC自適應的調節策略。

4、Serial Old 收集器

Serial Old 收集器是一個老年代收集器,因為使用但執行緒收集,所以效率一般,使用標記-整理演算法

5、Parallel Old 收集器

Parallel Old 收集器是Parallel Scavenge收集器的老年代版本,使用多執行緒和標記-整理演算法。Parallel Old 收集器出現之後,吞吐量優先的收集器(Parallel Scavenge)終於有了比較名副其實的應用組合。在注重吞吐量以及CPU資源銘感的應用中,都可以優先考慮Parallel Scavenge 和 Parallel Old收集器。

6、CMS 收集器

CMS 收集器是一款以獲得最短停頓時間為目標的收集器,CMS 收集器是基於標記-清除演算法實現的。它的運作過程較前面幾個收集器稍微複雜一些。

HotSpot虛擬機器所使用的垃圾收集器對比

垃圾收集器 特點 適用年代 實現演算法
Serial收集器

1、單執行緒收集器,進行垃圾回收時必須暫停所有的工作執行緒

2、是虛擬機器執行在Client模式下的預設新生代收集器 

新生代 複製演算法
ParNew收集器

1、是Serial收集器的多執行緒版本,使用多執行緒進行垃圾回收外

2、是執行在Server模式下虛擬機器的首選新生代收集器

3、ParNew收集器是在開啟 -XX:+UseConcMarkSweepGC 選項後的預設新生代收集器,也可以通過制定 -XX:UseParNewGC 選項來強制使用它。

新生代 複製演算法
Parallel Scavenge收集器

1、使用多執行緒

2、Parallel Scavenge 收集器與其他收集器的關注點不同,CMS等收集器的關注點是儘可能縮短垃圾收集時間和使用者執行緒的停頓時間。而Parallel Scavenge 收集器的目標則是達到一個可控的吞吐量,所以Parallel Scavenge 收集器是一個吞吐量優先的收集器

新生代 複製演算法
Serial Old 收集器

1、單執行緒老年代收集器,效率一般。

老年代 標記-整理演算法
Parallel Old 收集器

1、Parallel Scavenge 收集器的老年代版本

老年代 標記-整理演算法
CMS 收集器

1、CMS(Concurrent Mark Sweep)以獲取最短的回收停頓時間為目標

2、收集過程包括:

      初始標記:標記GC Roots能直接關聯到的物件,速度很快。

      併發標記:進行GC Roots Tracing的過程。

      重新標記

      併發清除

3、整個過程中耗時最長的是併發標記和併發清除兩個過程,都是和工作執行緒併發執行

缺點:

1、對CPU資源比較敏感

2、CMS 收集器無法處理浮動垃圾

3、因為CMS收集器使用標記-清除演算法,所以會產生大量的空間碎片。好在CMS收集器提供了一個開關引數,用於在CMS收集器頂不住要進行FULL GC 的時候合併整理過程。因為合併整理是無法併發執行的,所以不得不延長停頓時間

老年代 標記-清除演算法
G1 收集器

G1收集器最前沿的成果之一,它有如下特點

1、並行,併發

2、分代收集

3、空間整合

4、可預測的停頓

新生代,老年代