1. 程式人生 > >Java 垃圾回收機制與幾種垃圾回收演算法

Java 垃圾回收機制與幾種垃圾回收演算法

一、如何確定某個物件是“垃圾”?

這一小節先了解一個最基本的問題:如果確定某個物件是“垃圾”?既然垃圾收集器的任務是回收垃圾物件所佔的空間供新的物件使用,那麼垃圾收集器如何確定某個物件是“垃圾”?通過什麼方法判斷一個物件可以被回收了。

在java中是通過引用來和物件進行關聯的,也就是說如果要操作物件,必須通過引用來進行。那麼很顯然一個簡單的辦法就是通過引用計數來判斷一個物件是否可以被回收。不失一般性,如果一個物件沒有任何引用與之關聯,則說明該物件基本不太可能在其他地方被使用到,那麼這個物件就成為可被回收的物件了。這種方式成為引用計數法

這種方式的特點是實現簡單,而且效率較高,但是它無法解決迴圈引用的問題,因此在Java中並沒有採用這種方式(Python採用的是引用計數法)。

為了解決這個問題,在Java中採取了可達性分析法。該方法的基本思想是通過一系列的“GC Roots”物件作為起點進行搜尋,如果在“GC Roots”和一個物件之間沒有可達路徑,則稱該物件是不可達的,不過要注意的是被判定為不可達的物件不一定就會成為可回收物件。被判定為不可達的物件要成為可回收物件必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成為可回收物件的可能性,則基本上就真的成為可回收物件了。

二、典型的垃圾收集演算法

在確定了哪些垃圾可以被回收後,垃圾收集器要做的事情就是開始進行垃圾回收,但是這裡面涉及到一個問題是:如何高效地進行垃圾回收。由於Java虛擬機器規範並沒有對如何實現垃圾收集器做出明確的規定,因此各個廠商的虛擬機器可以採用不同的方式來實現垃圾收集器,所以在此只討論幾種常見的垃圾收集演算法的核心思想。

1.Mark-Sweep(標記-清除)演算法

這是最基礎的垃圾回收演算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除演算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的物件,清除階段就是回收被標記的物件所佔用的空間。具體過程如下圖所示:

從圖中可以很容易看出標記-清除演算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生記憶體碎片,碎片太多可能會導致後續過程中需要為大物件分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。

2.Copying(複製)演算法

  為了解決Mark-Sweep演算法的缺陷,Copying演算法就被提了出來。它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體碎片的問題。具體過程如下圖所示:

這種演算法雖然實現簡單,執行高效且不容易產生記憶體碎片,但是卻對記憶體空間的使用做出了高昂的代價,因為能夠使用的記憶體縮減到原來的一半。很顯然,Copying演算法的效率跟存活物件的數目多少有很大的關係,如果存活物件很多,那麼Copying演算法的效率將會大大降低。

3.Mark-Compact(標記-整理)演算法(壓縮法)

為了解決Copying演算法的缺陷,充分利用記憶體空間,提出了Mark-Compact演算法。該演算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收物件,而是將存活物件都向一端移動,然後清理掉端邊界以外的記憶體。具體過程如下圖所示:

4.Generational Collection(分代收集)演算法

分代收集演算法是目前大部分JVM的垃圾收集器採用的演算法。它的核心思想是根據物件存活的生命週期將記憶體劃分為若干個不同的區域。一般情況下將堆區劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量物件需要被回收,而新生代的特點是每次垃圾回收時都有大量的物件需要被回收,那麼就可以根據不同代的特點採取最適合的收集演算法。

目前大部分垃圾收集器對於新生代都採取複製演算法,因為新生代中每次垃圾回收都要回收大部分物件,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的物件複製到另一塊Survivor空間中,然後清理掉Eden和剛才使用過的Survivor空間。

而由於老年代的特點是每次回收都只回收少量物件,一般使用的是標記-整理演算法(壓縮法)

三、典型的垃圾收集器

垃圾收集演算法是 記憶體回收的理論基礎,而垃圾收集器就是記憶體回收的具體實現。下面介紹一下HotSpot(JDK 7)虛擬機器提供的幾種垃圾收集器,使用者可以根據自己的需求組合出各個年代使用的收集器。

1.Serial/Serial Old收集器 是最基本最古老的收集器,它是一個單執行緒收集器,並且在它進行垃圾收集時,必須暫停所有使用者執行緒。Serial收集器是針對新生代的收集器,採用的是Copying演算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact演算法。它的優點是實現簡單高效,但是缺點是會給使用者帶來停頓。

2.ParNew收集器 是Serial收集器的多執行緒版本,使用多個執行緒進行垃圾收集。

3.Parallel Scavenge收集器 是一個新生代的多執行緒收集器(並行收集器),它在回收期間不需要暫停其他使用者執行緒,其採用的是Copying演算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。

4.Parallel Old收集器 是Parallel Scavenge收集器的老年代版本(並行收集器),使用多執行緒和Mark-Compact演算法。

5.CMS(Current Mark Sweep)收集器 是一種以獲取最短回收停頓時間為目標的收集器,它是一種併發收集器,採用的是Mark-Sweep演算法。

6.G1收集器 是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。因此它是一款並行與併發收集器,並且它能建立可預測的停頓時間模型。

參考資料:《深入理解Java虛擬機器》