[深入理解Java虛擬機器]<垃圾收集器與記憶體配置策略>

來源:互聯網
上載者:User

標籤:複製演算法   .com   階段   並且   類的方法   應用   類型   static   live   

Overview
  • 垃圾收集考慮三件事:
    1. 哪些記憶體需要回收?
    2. 什麼時候回收?
    3. 如何回收?
  • 重點考慮Java堆中動態分配和回收的記憶體。
Is Object alive?引用計數法
  • 給對象添加一個引用計數器。
  • 該方法實現簡單,判定效率高。但是它很難解決對象之間相互循環參考的問題,因此幾乎很少有JVM選用該方法。eg:
    public class ReferenceCountingGC {  public Object instance = null;  // 占點記憶體,以便在GC日誌中看清楚是否被回收過  private static final int _1MB = 1024 * 1024;  private byte[] bigSize = new byte[2 * _1MB];    public static void testGC() {    ReferenceCountingGC objA = new ReferenceCountingGC();    ReferenceCountingGC objB = new ReferenceCountingGC();    objA.instance = objB;    objB.instance = objA;    objA = null;    objB = null;       System.gc();  }}
可達性分析演算法
  • 在主流商用程式語言(Java, C#, Lisp)的主流實現中,都是通過可達性分析(Reachability Analysis)來判斷對象是否存活的。
  • 該演算法的基本思路是通過一系列的稱為"GC Roots"的對象作為起始點,從這些節點開始向下搜尋。搜尋的路徑稱為引用鏈(Reference Chain)。
  • 當一個對象到GC Roots沒有任何引用鏈相連(即不可達)時,則該對象是停用。不可達的對象是可回收的。如下:
  • 在Java中,可作為GC Roots的對象包括下面幾種:
    • 虛擬機器棧(棧幀中的本地變數表)中的引用;
    • 方法區中類靜態屬性引用的對象;
    • 方法區中常量引用的對象;
    • 本地方法棧中JNI引用的對象。
再談引用
  • 上面兩種方法都是通過“引用”來判定對象是否alive。
  • 但是在JDK1.2之前,Java中的引用定義很傳統:若reference類型的資料中儲存的數值是另一塊記憶體的起始地址,則稱這塊記憶體代表著一個引用。
  • 但是考慮這樣的情境:我們希望描述這樣的一類對象,當記憶體空間足夠時,則能保留在記憶體中,否則可拋棄。 <-- 很多系統的緩衝功能都符合這樣的應用情境。
  • 於是在JDK1.2之後,Java擴充了引用的概念:將引用分為強引用(Strong Reference), 軟引用(Soft Reference), 弱引用(Weak Reference), 虛引用(Phantom Reference) 4種,強度依次減弱。
    • 強引用:最常見的,類似Object obj = new Object()
    • 軟引用:用以描述一些還有用但是非必須的對象。對於該類對象,系統在將要發生記憶體溢出異常之前,會回收這些對象。JDK1.2之後提供了SoftReference類。
    • 弱引用:也是用以描述非必須對象,但強度更弱於軟引用。被弱引用關聯的對象只能生存到下一次GC之前,而無論當前記憶體是否足夠。
    • 虛引用:PhantomReference類。
生存還是死亡
  • 即使是在可達性分析演算法中不可達的對象,也並非“非死不可”的。
  • 要真正宣告一個對象死亡,至少要經曆兩次標記過程:TBD...
回收方法區
  • 一般來說,在方法區進行垃圾收集的"性價比"比較低:在堆中,尤其是新生代中,常規應用一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低於此。
  • 永久代的垃圾收集主要回收兩部分:廢棄常量和無用的類。
  • 回收廢棄常量與回收Java堆中的對象非常相似。假設一個字串"abc"已經進入常量池,但當前系統中沒有任何一個String對象引用常量池中的"abc"常量,則"abc"常量就會被系統清理出常量池。
  • 相比之下判定一個類是否是“無用的類”的條件則相對苛刻很多。需同時滿足:
    • 該類所有的執行個體已經被回收;
    • 載入該類的ClassLoader已經被回收;
    • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾收集演算法標記-清除演算法
  • 演算法分為“標記”和“清除”兩個階段。
  • 該演算法是後續收集演算法的基礎。
  • 不足:
    • 效率問題:標記和清除兩個過程的效率都不高;
    • 空間問題:標記清除後會產生大量不連續的記憶體片段,空間片段太多可能導致以後在程式運行過程中需要分配較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次GC。

  

複製演算法
  • 它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。
  • 當使用的塊用完時,就將還存活著的對象複製到另一塊上面,然後再將已使用過的記憶體空間一次清理掉。
  • 這樣使得每次都是對整個半區進行記憶體回收,記憶體配置時也不用考慮記憶體片段等情況,只用移動堆頂指標,順序分配即可。
  • 代價是記憶體縮小為原來的一半。
  • 現在的商業JVM都採用這種收集演算法。IBM的專門研究表明,新生代中的對象98%是“朝生夕死”的,所以並不需要按照1:1的比例劃分記憶體空間。而是將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間。
標記-整理演算法
  • 複製收集演算法在對象存活率較高時就需要較多的複製操作,效率將會變低。更關鍵的是,若不想浪費一般的空間,就需要有額外的空間進行分配擔保,以應對被使用的記憶體中所有對象都100%存活的極端情況,所以老年代一般不能採用這種演算法。
  • 標記-整理演算法:標記過程仍同於標記-清除演算法。但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端外邊的記憶體。
分代收集演算法
  • 當前商業JVM的GC都採用“分代收集”(Generational Collection)演算法。
  • 這種演算法就是根據對象存貨周期地不同將記憶體劃分為幾塊。
  • 一般是把Java堆分為新聲代和老生代,從而根據各個年代的特點採用最適當的收集演算法。
Summary
  • 本篇主要覆蓋了GC的兩大問題:
    • 哪些對象需要回收:最基礎的是引用計數法,簡單但無法解決循環參考問題。在此基礎上,更常用的是可達性分析演算法。
    • 如何回收:最基礎的是標記-清除法,即先標記出哪些對象需要回收,然後逐一清除,不足是會產生大量記憶體片段,從而導致後續可能觸發多次GC。此外更高效的是複製法,該方法確保了連續記憶體,但是將記憶體縮小至原記憶體一般,並且在對象存活率較高時會引入大量複製操作,效率降低。還有標記-整理演算法。 另外,商業JVM都會採用分代收集演算法,即將記憶體劃分為幾塊,根據年代選取合適的收集演算法。

[深入理解Java虛擬機器]<垃圾收集器與記憶體配置策略>

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.