Java虛擬機垃圾回收(二) 垃圾回收算法 標記-清除算法 複製算法 標記-整理算法 分代收集算法 火車算法

       在《Java虛擬機垃圾回收(一) 基礎》中瞭解到如何判斷對象是存活還是已經死亡? 介紹了垃圾回收基礎算法:引用計數算法、可達性分析算法,以及HotSpot虛擬機中實現對象可達性分析的一些問題。

       下面先來了解Java虛擬機垃圾回收的幾種常見算法:標記-清除算法、複製算法、標記-整理算法、分代收集算法、火車算法,介紹它們的算法思路,有什麼優點和缺點,以及主要應用場景。

1、標記-清除算法
       標記-清除(Mark-Sweep)算法是一種基礎的收集算法。

1、算法思路

       "標記-清除"算法,分爲兩個階段:

(A)、標記

      首先標記出所有需要回收的對象;

       標記過程如《Java虛擬機垃圾回收(一) 基礎》"2-4、判斷對象生存還是死亡"中所述--分爲兩個標記過程(詳細請參考前文):

(1)、第一次標記

       在可達性分析後發現對象到GC Roots沒有任何引用鏈相連時,被第一次標記;

       並且進行一次篩選:此對象是否必要執行finalize()方法;

       對有必要執行finalize()方法的對象,被放入F-Queue隊列中;    

(2)、第二次標記

       GC將對F-Queue隊列中的對象進行第二次小規模標記;

       在其finalize()方法中重新與引用鏈上任何一個對象建立關聯,第二次標記時會將其移出"即將回收"的集合;

       對第一次被標記,且第二次還被標記(如果需要,但沒有移出"即將回收"的集合),就可以認爲對象已死,可以進行回收。

(B)、清除

      兩次標記後,還在"即將回收"集合的對象將被統一回收;

       執行過程如下圖:

2、優點

       基於最基礎的可達性分析算法,它是最基礎的收集算法;

       而後續的收集算法都是基於這種思路並對其不足進行改進得到的;

3、缺點

       主要有兩個缺點:

(A)、效率問題

       標記和清除兩個過程的效率都不高;

(B)、空間問題

       標記清除後會產生大量不連續的內存碎片;

       這會導致分配大內存對象時,無法找到足夠的連續內存;

       從而需要提前觸發另一次垃圾收集動作;

4、應用場景

      針對老年代的CMS收集器;

2、複製算法算法
       "複製"(Copying)收集算法,爲了解決標記-清除算法的效率問題;

1、算法思路

       (A)、把內存劃分爲大小相等的兩塊,每次只使用其中一塊;

       (B)、當一塊內存用完了,就將還存活的對象複製到另一塊上(而後使用這一塊);

       (C)、再把已使用過的那塊內存空間一次清理掉,而後重複步驟2;    

      執行過程如下圖:

2、優點

       這使得每次都是隻對整個半區進行內存回收;

       內存分配時也不用考慮內存碎片等問題(可使用"指針碰撞"的方式分配內存);

      實現簡單,運行高效;

       (關於"指針碰撞"請參考《Java對象在HotSpot虛擬機中的創建過程》)

3、缺點

(A)、空間浪費

      可用內存縮減爲原來的一半,太過浪費(解決:可以改良,不按1:1比例劃分);

(B)、效率隨對象存活率升高而變低

      當對象存活率較高時,需要進行較多複製操作,效率將會變低(解決:後面的標記-整理算法);

4、應用場景

      現在商業JVM都採用這種算法(通過改良缺點1)來回收新生代;

      如Serial收集器、ParNew收集器、Parallel Scavenge收集器、、G1(從局部看);

5、HotSpot虛擬機的改良算法

(A)、弱代理論

       分代垃圾收集基於弱代理論(weak generational hypothesis),具體描述如下:

       (1)、大多數分配了內存的對象並不會存活太長時間,在處於年輕代時就會死掉;

       (2)、很少有對象會從老年代變成年輕代;

       其中IBM研究表明:新生代中98%的對象都是"朝生夕死";

        所以並不需要按1:1比例來劃分內存(解決了缺點1);

(B)、HotSpot虛擬機新生代內存佈局及算法

                      (1)、將新生代內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間;

                      (2)、每次使用Eden和其中一塊Survivor;

                      (3)、當回收時,將Eden和使用中的Survivor中還存活的對象一次性複製到另外一塊Survivor;

                      (4)、而後清理掉Eden和使用過的Survivor空間;

                      (5)、後面就使用Eden和複製到的那一塊Survivor空間,重複步驟3;

         默認Eden:Survivor=8:1,即每次可以使用90%的空間,只有一塊Survivor的空間被浪費;

(C)、分配擔保

       如果另一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象時,這些對象將直接通過分配擔保機制(Handle Promotion)進入老年代;

       分配擔保在以後講解垃圾收集器執行規則時再詳解;

       更多請參考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html#sthref16

3、標記-整理算法
       "標記-整理"(Mark-Compact)算法是根據老年代的特點提出的。

1、算法思路

(1)、標記

      標記過程與"標記-清除"算法一樣;

(2)、整理

       但後續不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動;

       然後直接清理掉端邊界以外的內存;

執行過程如下圖:

2、優點

(A)、不會像複製算法,效率隨對象存活率升高而變低

       老年代特點:

       對象存活率高,沒有額外的空間可以分配擔保;

       所以老年代一般不能直接選用複製算法算法;

       而選用標記-整理算法;

(B)、不會像標記-清除算法,產生內存碎片

       因爲清除前,進行了整理,存活對象都集中到空間一側;

3、缺點

       主要是效率問題:除像標記-清除算法的標記過程外,還多了需要整理的過程,效率更低;

4、應用場景

       很多垃圾收集器採用這種算法來回收老年代;

      如Serial Old收集器、G1(從整體看);

4、分代收集算法
       "分代收集"(Generational Collection)算法結合不同的收集算法處理不同區域。

1、算法思路

       基於前面說的弱代理論,其實並沒有什麼新的思想;

       只是根據對象存活週期的不同將內存劃分爲幾塊;

       這樣就可以根據各個年代的特點採用最適當的收集算法;

       一般把Java堆分爲新生代和老年代;

(A)、新生代

       每次垃圾收集都有大批對象死去,只有少量存活;

       所以可採用複製算法;

(B)、老年代

       對象存活率高,沒有額外的空間可以分配擔保;

      使用"標記-清理"或"標記-整理"算法;

      結合上面對新生代的內存劃分介紹和上篇文章對Java堆的介紹,可以得出HotSpot虛擬機一般的年代內存劃分,如下圖:

2、優點      

       可以根據各個年代的特點採用最適當的收集算法;

3、缺點      

       仍然不能控制每次垃圾收集的時間;
4、應用場景

      目前幾乎所有商業虛擬機的垃圾收集器都採用分代收集算法;

      如HotSpot虛擬機中全部垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1(也保留);

5、火車算法
      火車算法也稱列車算法,是一種更徹底的分區域處理收集算法,是對分代收集算法的一個有力補充。

1、算法思路

      在火車算法中,內存被分爲塊,多個塊組成一個集合。爲了形象化,一節車廂代表一個塊,一列火車代表一個集合,如下圖;

      火車與車箱都按創建順序標號,每個車廂大小相等,但每個火車包含的車廂數不一定相等;

      每節車箱有一個被記憶集合,而每輛火車的記憶集合是它所有車廂記憶集合的總和;

      記憶集合由指向車箱中對象的引用組成,這些引用來自同一輛火車中序號較高的車箱中的對象,以及序號較高中的對象;

      垃圾收集以車廂爲單位,整體算法流程如下:

(1)、選擇標號最小的火車;

(2)、如果火車的記憶集合是空的, 釋放整列火車並終止, 否則進行第三步操作;

(3)、選擇火車中標號最小的車廂;

(4)、對於車廂記憶集合的每個元素:

      如果它是一個被根引用引用的對象, 那麼, 將拷貝到一列新的火車中去;

      如果是一個被其它火車的對象指向的對象, 那麼, 將它拷貝到這個指向它的火車中去.;

      假設有一些對象已經被保留下來了, 那麼通過這些對象可以觸及到的對象將會被拷貝到同一列火車中去;

      如果一個對象被來自多個火車的對象引用, 那麼它可以被拷貝到任意一個火車去;

      這個步驟中, 有必要對受影響的引用集合進行相應地更新;

(5)、釋放車廂並且終止;

      收集過程會刪除一些空車箱和空車,當需要的時候也會創建一些車箱和火車,更多信息請參考:《編譯原理》第二版7.75"列車算法"、《漸進式地垃圾回收: 火車算法》;

      執行過程如下圖:

2、優點

      可以在成熟對象空間提供限定時間的漸近收集;

      而不需要每次都進行一個大區域的垃圾回收過程;

      即可以控制垃圾回收的時間,在指定時間內進行一些小區域的回收;

3、缺點

      實現較爲複雜,如採用類似的算法的G1收集器在JDK7才實現;

      一些場景下可能性價比不高;

4、應用場景

      JDK7後HotSpot虛擬機G1收集器採用類似的算法,能建立可預測的停頓時間模型;

 

      到這裏,我們大體瞭解Java虛擬機垃圾回收的幾種常見算法,後面我們將分別去了解JVM垃圾收集器、以及相關調優方法……

 

【參考資料】

1、《編譯原理》第二版 第7章

2、《深入理解Java虛擬機:JVM高級特性與最佳實踐》第二版 第3章

3、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

4、《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html

5、《Memory Management in the Java HotSpot™ Virtual Machine》:http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf

6、HotSpot虛擬機參數官方說明:http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

7、《Thinking in Java》第四版 5.5 清理:終結處理和垃圾回收;

8、漸進式地垃圾回收: 火車算法:http://nileader.blog.51cto.com/1381108/402609
————————————————

原文鏈接:https://blog.csdn.net/tjiyu/article/details/53983064

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