1. 程式人生 > >JVM GC調優(3)-----GC演算法(部分摘自深入理解Java虛擬機器)

JVM GC調優(3)-----GC演算法(部分摘自深入理解Java虛擬機器)

介紹幾種GC演算法的思想及其發展過程:

  • 標記-清除
  • 複製
  • 標記-壓縮
  • 分代收集演算法

GC演算法主要是用於堆死亡物件的清理的集中方式,他們各有優缺點,下面我們開始做介紹

標記清除演算法

在這裡插入圖片描述

  • 原理解析 -最基礎的收集演算法是“標記-清除”( Mark-Sweep) 演算法, 如同它的名字一樣, 演算法分為“標記”和“清除”兩個階段: 首先標記出所有需要回收的物件, 在標記完成後統一回收所有被標記的物件, 它的標記過程其實在前一篇講述物件標記判定時已經介紹過了。
  • 缺點:
    1. 效率問題, 標記和清除兩個過程的效率都不高;
    2. 標記清除之後會產生大量不連續的記憶體碎片, 空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時, 無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

複製演算法

在這裡插入圖片描述

  • 原理解析 為了解決效率問題, 一種稱為“複製”( Copying) 的收集演算法出現了, 它將可用記憶體按容量劃分為大小相等的兩塊, 每次只使用其中的一塊。 當這一塊的記憶體用完了, 就將還存活著的物件複製到另外一塊上面, 然後再把已使用過的記憶體空間一次清理掉。 這樣使得每次都是對整個半區進行記憶體回收, 記憶體分配時也就不用考慮記憶體碎片等複雜情況, 只要移動堆頂指標, 按順序分配記憶體即可, 實現簡單, 執行高效。 只是這種演算法的代價是將記憶體縮小為了原來的一半, 未免太高了一點。 複製演算法通過犧牲空間的方法,來提高執行效率。
  • 應用

這裡有一個重要的概念需要引出,就是基於複製演算法的對記憶體分佈。因此需要特別提示

  • 思想的提出 現在的商業虛擬機器都採用這種收集演算法來回收新生代, IBM公司的專門研究表明, 新生代中的物件98%是“朝生夕死”的, 所以並不需要按照1:1的比例來劃分記憶體空間, 而是將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間, 每次使用Eden和其中一Survivor。當回收時, 將Eden和Survivor中還存活著的物件一次性地複製到另外一塊Survivor空間上, 最後清理掉Eden和剛才用過的Survivor空間。
  • 虛擬機器的應用 HotSpot虛擬機器預設Eden和Survivor1,Survivor2的大小比例是8:1:1, 也就是每次新生代中可用記憶體空間為整個新生代容量的90%( 80%+10%) , 只有10%的記憶體會被“浪費”(最為複製後物件的儲存區域)。
  • 物件大於10%記憶體的情況 當然, 98%的物件可回收只是一般場景下的資料, 我們沒有辦法保證每 次回收都只有不多於10%的物件存活, 當Survivor空間不夠用時, 需要依賴其他記憶體( 這裡指老年代) 進行分配擔保( Handle Promotion) 。
  • 記憶體的分配擔保 記憶體的分配擔保就好比我們去銀行借款, 如果我們信譽很好, 在98%的情況下都能按時償還, 於是銀行可能會預設我們下一次也能按時按量地償還貸款, 只需要有一個擔保人能保證如果我不能還款時, 可以從他的賬戶扣錢, 那銀行就認為沒有風險了。 記憶體的分配擔保也一樣, 如果另外一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活物件時,這些物件將直接通過分配擔保機制進入老年代。

標記-整理演算法

在這裡插入圖片描述 當存活物件較多的時候,複製演算法效率較低,因此在老年代這種特殊的區域,我們一般使用的是標記-壓縮演算法。

  • 原理 標記過程 仍然與“標記-清除”演算法一樣, 但後續步驟不是直接對可回收物件進行清理, 而是讓所有存活的物件都向一端移動, 然後直接清理掉端邊界以外的記憶體,如上圖顯示 。

分代收集演算法(分為新生代和老年代)

當前商業虛擬機器的垃圾收集都採用“分代收集”( Generational Collection) 演算法, 這種演算法並沒有什麼新的思想, 只是根據物件存活週期的不同將記憶體劃分為幾塊。 一般是把Java堆分為新生代和老年代, 這樣就可以根據各個年代的特點採用最適當的收集演算法。 在新生代中, 每次垃圾收集時都發現有大批物件死去, 只有少量存活, 那就選用複製演算法, 只需要付出少量存活物件的複製成本就可以完成收集。 而老年代中因為物件存活率高、 沒有額外空間對它進行分配擔保, 就必須使用“標記—清理”或者“標記—整理”演算法來進行回收。 在這裡插入圖片描述