1. 程式人生 > >深入理解java虛擬機器之自動記憶體管理機制(三)

深入理解java虛擬機器之自動記憶體管理機制(三)

 

各類垃圾收集器與GC日誌

(一)垃圾收集器

  一、Serial收集器

    最基本、歷史最悠久的收集器。使用複製演算法,用在新生代,通常老年代用Serial old配合。GC過程需要stop the world。適用於client模式下的虛擬機器。

  二、ParNew收集器

    Serial多執行緒版本,採用複製演算法時,開啟多執行緒進行復制。能與Serial old配合,且是唯一能與cms收集器配合使用的新生代收集器。適用於server模式下的虛擬機器。在多cpu的環境
  下效果更好。

  三、Parallel Scavenge收集器

    關注點是達到一個可控制的吞吐量,使用複製演算法。吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)。

  四、Serial Old收集器

    Serial收集器的老年代版本,單執行緒收集器,使用標記-整理演算法,適用於Client模式下的虛擬機器。

  五、Parallel Old收集器

    Parallel Scavenge老年代版本,使用標記-整理演算法。

  六、CMS收集器

    以獲得最短回收停頓時間為目標的收集器。運作過程分為4個步驟。

    1. 初始標記。該階段就是標記一下GC Roots能直接關聯的物件。

    2. 併發標記。就是GC Roots tracing,該過程可與使用者執行緒併發執行。

    3. 重新標記。修正併發標記期間發生變動的物件的標記。

    4. 併發清除。該過程可與使用者執行緒併發執行。

    缺點:

    1. 對cpu資源敏感。如果不是多核,則由於存在兩個併發過程,會導致使用者程式執行緩慢。

    2. 無法處理浮動垃圾,並且由於在清除階段與使用者執行緒併發執行,可能由於記憶體不足而導致一次full gc的產生。所謂浮動垃圾,就是在重新標記過後產生的垃圾。解決辦法是預留足夠記憶體。

    3. 由於採用標記-清除演算法,所以容易產生大量記憶體碎片。

  七、G1收集器

    G1是革命性的。它有以下幾個特點。

    1. 並行與併發。G1可以讓本來需要stw的動作通過併發的方式讓java程式繼續執行。

    2. 分代收集。G1收集器自身就可以管理新生代和老年代的物件。

    3. 空間整合。整體使用標記-整理演算法,區域性使用複製演算法。

    4. 可預測的停頓。G1會建立可預測的停頓時間模型。

 

    G1與傳統收集器不同的地方:

    G1將java堆分為許多個大小相同的獨立區域(Region),新生代和老年代之間不再是物理隔離的,它們都是一部分Region的集合。

 

    G1為什麼能建立可預測的停頓時間模型?

      G1可以有計劃地避免在Java堆的進行全區域的垃圾收集,通過跟蹤各個Region獲得其收集價值大小(回收所獲得的空間大小以及回收所需要的時間的經驗值),在後臺維護一個優先列表

    每次根據允許的收集時間,優先回收價值最大的Region(名稱Garbage-First的由來),這就保證了在有限的時間內可以獲取儘可能高的收集效率。

 

    G1如何解決一個物件被不同區域引用的問題?

      不止G1要解決這個問題,其他收集器也要解決這個問題,其他收集器需要解決新生代與老年代之間的物件引用問題。都是通過使用Rememed Set來避免全堆掃描。G1的每個Region都有一

    個與之對應的Remembered Set,虛擬機發現程式在對引用型別的資料進行讀寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查引用型別引用的物件是否處於不同的Region之中,如果是,就

    通過CardTable把相關引用資訊記錄到被引用物件所屬的Region的Remembered Set之中。當進行記憶體回收時,在Gc根節點列舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

 

    關於OopMap 和 Remembered Set

      總的來說:OopMap用於列舉根節點,Remembered Set 用來作可達性分析。

      OopMap避免了全棧掃描,Remembered Set避免了全堆掃描。

      新生代 GC(發生得非常頻繁)。一般來說, GC過程是這樣的:首先列舉根節點。根節點有可能在新生代中,也有可能在老年代中。這裡由於我們只想收集新生代(換句話說,不想收集老年代),

    所以沒有必要對位於老年代的 GC Roots 做全面的可達性分析。但問題是,確實可能存在位於老年代的某個 GC Root,它引用了新生代的某個物件,這個物件你是不能清除的。那怎麼辦呢?

      與OopMap一樣,用空間換時間,用一個數據結構儲存這種引用資訊,這樣在只需要再新生代上利用這兩個東西就能完成可達性的分析。RememberedSet記錄的是新生代的物件被老年代引用的關係。

    所以“新生代的 GC Roots ” + “ RememberedSet 儲存的內容”,才是新生代收集時真正的 GC Roots 。

    參考:https://dsxwjhf.iteye.com/blog/2201685

                    https://blog.csdn.net/ifleetingtime/article/details/78934379

    

    G1的運作過程(不計維護Remembered Set)

    1. 初始標記(Initial Marking)           

      僅標記一下GC Roots能直接關聯到的物件;

         且修改TAMS(Next Top at Mark Start),讓下一階段併發執行時,使用者程式能在正確可用的Region中建立新物件;
         需要"Stop The World",但速度很快;


    2. 併發標記(Concurrent Marking)
       進行GC Roots Tracing
       耗時較長,但與使用者執行緒併發執行;
  
    3. 最終標記(Final Marking)
       為了修正併發標記期間變動的物件的標記記錄
       上一階段物件的變化記錄線上程的Remembered Set Log;
       這裡把Remembered Set Log合併到Remembered Set中;
         需要"Stop The World",且停頓時間比初始標記稍長,但遠比並發標記短;
    採用多執行緒並行執行來提升效率;
         
  4. 篩選回收(Live Data Counting and Evacuation)
          首先排序各個Region的回收價值和成本;
          然後根據使用者期望的GC停頓時間來制定回收計劃;
          最後按計劃回收一些價值高的Region中垃圾物件;
          回收時採用"複製"演算法,從一個或多個Region複製存活物件到堆上的另一個空的Region,並且在此過程中壓縮和釋放記憶體;

 

(二)GC日誌

      https://blog.csdn.net/xiaodu93/article/details/61926114