天天看點

GC四種垃圾回收算法

引用計數(Reference Counting)算法

  • 引用計數算法在每個對象都維護着一個記憶體字段來統計它被多少”部分”使用—引用計數器,每當有一個新的引用指向該對象時,引用計數器就+1 ,每當指向該引用對象失效時該計數器就-1 ,當引用數量為0的時候,則說明對象沒有被任何引用指向,可以認定是”垃圾”對象.
  • 由于隻維護局部資訊,是以不需要掃描全局對象圖就可以識别并釋放死對象;但也因為缺乏全局對象圖資訊,是以無法處理循環引用的狀況。更進階的引用計數實作會引入“弱引用”的概念來打破某些已知的循環引用,但那是另一個話題了。
  • 至于存放引用計數器的位置放在哪? RednaxelaFX介紹了兩種方案. 可以侵入式的存在對象内,例如CPython就把引用計數存在每個受自動記憶體管理的Python對象的對象頭裡(PyObject的ob_refcnt字段),或者COM的IUnknown::AddRef()/Release();也可以非侵入式的存在對象外面,例如C++11标準庫裡的std::shared_ptr。
  • 下面通過網上一段非常常見的代碼來分析為什麼會産生循環引用的問題,目前隻看到Gityuan有一段說明感覺是非常清晰地說明了這段代碼:
public static void main(String[] args) {
        GcObject obj1 = new GcObject(); //Step1
        GcObject obj 2 = new GcObject();//Step2
        obj1.instance = obj2; //Step3
        obj2.instance = obj1;// //Step4
        obj1 = null; //Step5
        obj2 = null; //Step6
    }
           
當采用引用計數算法時:
           

第一步:GcObject執行個體1被obj1引用,是以它的引用數+1,為1

第二步:GcObject執行個體2被obj2引用,是以它的引用數+1,為1

第三步:obj1的instance屬性指向obj2,而obj2指向GcObject執行個體2,故GcObject執行個體2引用+1,為2

第四步:obj2的instance屬性指向obj1,而obj1指向GcOjbect執行個體1,故GcObject執行個體1引用+1,為2

到此前4步, GcOjbect執行個體1和GcOjbect執行個體2的引用數量均為2,此時結果圖如下.

PS:注意想一下,為什麼是obj的instance屬性,而不是寫成obj本身?

GC四種垃圾回收算法

5.第五步:obj1不再指向GcOjbect執行個體1,其引用計數減1,結果為1.

6.第六步:obj2不再指向GcOjbect執行個體2,其引用計數減1,結果為1.

到此,發現GcObject執行個體1和執行個體2的計數引用都不為0,那麼如果采用的引用計數算法的話,那麼這兩個執行個體所占的記憶體将得不到釋放,這便産生了記憶體洩露。

引用計數算法常用來說明垃圾回收算法的機制,但是很少為各種語言所有,其中COM采用的就是引用計數算法.

前面引用RednaxelaFX的話時,他提到過一種進階的引用計數算法采用了”弱引用”來解決了部分已知的循環引用.

标記一清除( Mark-Sweep )算法:

  • 首先标記出存活的對象,那些沒有被标記的就可以 被收集了。此算法執行分兩階段。第一階段從引用根節點開始标記所有被引用的對象, 第二階段周遊整個堆,把未标記的對象清除。他的主要不足有兩個:一個是效率問題,标記和清除兩個過程的效率都不高;另一個是空間問題,标記清除之後會産生大量不連續的記憶體碎片,空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大的對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作, 此算法需要暫停整個應用;
GC四種垃圾回收算法

複制( Copying )算法:

  • 将記憶體分成兩部分,收集的時候,存活的對象從一部分被移動 到另一個部分,如此往複。此算法把記憶體空間劃分為兩個相等的區域,每次隻使用其中 一個區域。垃圾回收時,周遊目前使用區域, 把正在使用中的對象複制到另外一個區域 中。然後清理使用過得記憶體空間,此算法每次隻處理正在使用中的對象,是以複制成本比較小,同時複制過去以後還 能進行相應的記憶體整理,不會出現“碎片”問題,隻要移動堆頂指針,按順序配置設定記憶體即可,實作簡單,運作高效。當然,此算法的缺點也是很明顯的, 就是需要兩倍的記憶體空間(或者将記憶體縮小為了原來的一半)。
GC四種垃圾回收算法

标記-整理( Mark-Compact )算法:

  • 此算法結合了“标記,清除”和“複制”兩個算法 的優點。也是分兩階段,第一階段從根節點開始标記所有被引用對象,第二階段周遊整 個堆,清除未标記對象并且把存活對象“壓縮”到堆的其中一塊連續的區域,按順序排放。此算法 避免了“标記-清除”的碎片問題,同時也避免了“複制”算法的空間問題。标記,整理 算法
GC四種垃圾回收算法

分代收集( Generational Collecting )算法:

  • 這種算法并沒有什麼新的思想,隻是根據對象存活周期的不同将記憶體劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最适合的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,隻有少量存活,那就選用複制算法,隻需要付出少量存活對象的複制成本就可以完成。而老年代中因為對象存活率高、沒有額外空間對他進行配置設定擔保,就必須使用“标記-清理”或者“标記-整理”算法來進行回收。

    ————————————————

備注:

GC四種垃圾回收算法