GC的歷史比Java還有久遠,我們在思考GC時候需要思考三個問題:
- 哪些內存需要回收?
- 什麼時候回收?
- 如何回收?
在Java中程序計數器、虛擬機棧、本地方法棧這三個區域隨線程而生,隨線程而滅:棧中的棧幀隨着方法的調用和退出而有條不紊的進行着入棧和出棧的過程。每個棧幀分配多少內存在類結構確定下來時就已知的,方法結束或者線程結束內存自然跟着回收了。
而Java堆和方法區不一樣,一個接口中的多個實現類的內存可能不一樣,每個方法的多個分支需要的內存也可能不一樣,我們只有在程序運行時候才知道會創建哪些對象,這部分內存的分配和回收都是動態的。
一、判斷對象已死的算法
(一)引用計數算法
給對象添加一個引用計數器,每當一個地方引用它時候,計數器就加1,當引用失效,計數器就減1;任何時刻計數器爲0的對象就是不可能再被使用了。這種方法實現簡單,效率高,但是它很難解決對象的循環引用問題:
public class Test {
private static final int _1MB = 1024 *1024;
private Object instance = null;
public static void testGC(){
Test objectA = new Test();
Test objectB = new Test();
objectA.instance = objectB;
objectB.instance = objectA;
objectA = null;
objectB = null;
// 假如這裏發生GC,objectA和objectB會不會被回收
}
}
(二)可達性分析算法
這個算法的基本思路是通過一系列稱爲“GC Roots”(一組必須活躍的引用)作爲起始點,從這些節點開始向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈時候,那麼證明此對象是不可用的。
在Java語言中,做作爲GC Roots的對象包括以下幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象。
- 方法區中類靜態屬性引用的對象。
- 方法區中常量引用的對象。
- 本地方法棧JNI(即一般說的Native方法)引用的對象。
二、引用
無論是通過引用計數器算法判斷對象的引用數量,還是通過可達性分析算法判斷對象引用鏈是否可達,判斷對象是否可活都離不開引用,Java中將引用分爲四種:
(一)強引用(Strong Reference)
是指程序代碼中普遍存在的,類似“Object obj = new Object()”這類的引用,只有強引用還存在對象就不會被回收。
(二)軟引用(Soft Reference)
軟引用是用來描述一些還有用但是非必須的對象。對於軟引用關聯的對象,在系統將於發生內存溢出異常之前,將會把這些對象列進回收範圍中進行二次回收。
(三)弱引用(Weak Reference)
也是用來描述非必須對象的,強度比軟引用還弱一些,被軟引用關聯的對象只能存活到下一次內存回收之前。
(四)虛引用(Phantom Referenece)
也成爲幽靈引用和幻影引用,爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被回收時收到一個系統通知。
三、生存還是死亡
即使在可達性分析算法中不可達的對象,也並非一定是“非死不可”的,這時候他們暫時處於“緩刑”階段,真正宣告一個對象死亡至少要經歷兩個階段:
1)如果對象在可達性分析算法中不可達,那麼它會被第一次標記並進行一次刷選,刷選的條件是是否需要執行finalize()方法(當對象沒有覆蓋finalize()或者finalize()方法已經執行過了(對象的此方法只會執行一次)),虛擬機將這兩種情況都會視爲沒有必要執行)。
2)如果這個對象有必要執行finalize()方法會將其放入F-Queue隊列中,稍後GC將對F-Queue隊列進行第二次標記,如果在重寫finalize()方法中將對象自己賦值給某個類變量或者對象的成員變量,那麼第二次標記時候就會將它移出“即將回收”的集合。
finalize()能做的工作,使用try-finally或者其他方式都能做到更好,更及時,所以不建議使用此方法。
四、方法區回收
永久代中回收的內容主要是兩部分:
廢棄的常量和無用的類。
判斷無用的類(類卸載)必須滿足三個條件:
1)該類所有的實例都已經被回收
2)加載該類的ClassLoader被回收
3)該類對應的java.lang.Class對象沒有在任何地方引用,無法在任何地方通過反射訪問該類的方法