GC的出現解放了程式設計師需要手動回收記憶體的苦惱,但我們也是要了解GC的,知己知彼,百戰不殆嘛。 常見的GC回收演算法主要包括引用計數演算法、標記清除演算法、複製演算法、標記壓縮演算法、分代演算法以及分割槽演算法。
今天來聊聊引用計數演算法。
1原理
顧名思義,此種演算法會在每一個物件上記錄這個物件被引用的次數,只要有任何一個物件引用了次物件,這個物件的計數器就+1,取消對這個物件的引用時,計數器就-1。任何一個時刻,如果該物件的計數器為0,那麼這個物件就是可以回收的。
打個比方:
public static void method() {
A a = new A();
}
public static void main(String[] args) {
method();
}
複製程式碼
main函式呼叫method方法,method方法中new了一個A的物件,賦值給區域性變數a,此時堆記憶體中的物件A的例項的計數器就會+1。當方法結束時,區域性變數會隨之銷燬,堆記憶體中的物件的計數器就會-1。
2存在的問題
該演算法存在兩個問題:
(1)無法處理迴圈引用的情況。
(2)從上述的原理可知,堆內物件的每一次引用賦值和每一次引用清除,都伴隨著加減法的操作,會帶來一定的效能開銷。
所以Java沒有使用這種演算法來實現GC。
下面來解釋一下第一個問題,迴圈引用的情況。
即物件A引用物件B,物件B引用物件A。
考慮如下程式碼:
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a = new A();
public void setA(A a) {
this.a = a;
}
}
public void method() {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
複製程式碼
其記憶體圖示如下
method方法中,執行完兩個set後,method方法結束,圖中兩條紅線引用消失,可以看到,留下兩個物件在堆記憶體中迴圈引用,但此時已經沒有地方在用他們了,造成記憶體洩漏。兩個物件就凌亂在風中不知所措了。