GC演算法與種類
(一)GC的概念
GC,指Ganbage Collection 記憶體回收行程。GC的演算法主要分為四類:引用計數法、標記清除、標記壓縮、複製演算法。下面將對這幾種演算法進行逐一說明。
(二)GC的演算法——引用計數法
引用計數器的實現很簡單,對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要對象A的引用計數器的值為0,則對象A就不可能再被使用。
引用計數法的問題:
(1)引用和去引用伴隨著加法和減法,影響效能;
(2)很難處理循環參考;
(二)GC的演算法——標記-清除演算法
標記-清除演算法是現代記憶體回收演算法的思想基礎。標記-清除演算法將記憶體回收分為兩個階段:
(1)標記階段:通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象。
(2)清除階段:清除所有未被標記的對象。
(三)GC的演算法——標記-壓縮演算法
標記-壓縮演算法適合用於存活對象較多的場合,如老年代。它在標記-清除演算法的基礎上做了一些最佳化。和標記-清除演算法一樣,標記-壓縮演算法也首先需要從根節點開始,對所有可達對象做一次標記。但之後,它並不簡單的清理未標記的對象,而是將所有的存活對象壓縮到記憶體的一端。之後,清理邊界外所有的空間。
【引申】標記壓縮對標記清除而言,有什麼優勢呢。
優勢就是能夠整理記憶體片段,避免分配大對象時,空間不足導致FullGC。
(四)GC的演算法——複製演算法
與標記-清除演算法相比,複製演算法是一種相對高效的回收方法;
不適用於存活對象較多的場合 如老年代;
將原有的記憶體空間分為兩塊,每次只使用其中一塊,在記憶體回收時,將正在使用的記憶體中的存活對象複製到未使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有對象,交換兩個記憶體的角色,完成記憶體回收。
複製演算法的最大問題是:空間浪費!
(五)分代思想
依據對象的存活周期進行分類,短命對象歸為新生代,長命對象歸為老年代。
根據不同代的特點,選取合適的收集演算法:
①少量對象存活,比如新年代,適合複製演算法。
②大量對象存活,比如老生代,適合標記清理或者標記壓縮
【注意】所有的演算法,需要能夠識別一個垃圾對象,因此需要給出一個可觸及性的定義。
(六)可觸及性
(1)可觸及的:從根節點可以觸及到這個對象。
(2)可複活的:一旦所有引用被釋放,就是可複活狀態,因為在finalize()中可能複活該對象。
(3)不可觸及的:在finalize()後,可能會進入不可觸及狀態,不可觸及的對象不可能複活,可以回收。
看如下例子:
package com.liyan.gcTest;public class CanReliveObj {public static CanReliveObj obj;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("CanReliveObj finalize called");obj = this;}@Overridepublic String toString() {return "I am CanReliveObj";}public static void main(String[] args) throws InterruptedException {obj = new CanReliveObj();obj = null; // 可複活System.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 可用");}System.out.println("第二次gc");obj = null; // 不可複活System.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 可用");}}}
輸出結果
CanReliveObj finalize calledobj 可用第二次gcobj 是 null
【注】本文主要總結自網際網路共用課件,《煉數成金——深入JVM核心》作者:葛一鳴。僅供學習分享。