垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象中哪些還“存活”,哪些已經“死去”。
引用的分類
Java將引用分爲4種:
-
強引用:指在程序代碼之中普遍存在的引用賦值,即類似 “Object obj = new Object ()”
這種引用關係。無論何種情況下,只要強引用關係存在,垃圾收集器就不會回收調被引用的對象。 -
軟引用:用來描述一些還有用,但非必須的對象。只要被軟引用關聯着的對象,在系統將要發生內存溢出異常之前,會把這些對象列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。
-
弱引用:也是用來描述那些非必須對象,但它的強度比軟引用還弱一些。被弱引用關聯的對象只能生存到下一次垃圾收集發生爲止
-
虛引用:一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來獲取一個對象實例。
引用計數法
在對象中添加一個引用計數器,每當有一個地方引用它時,計數器就加一;當引用失效時,計數器值就減一;計數器爲零的對象就是不可能再被使用的。
然而,在Java中,並沒有選擇引用計數法來進行內存管理。
可達性分析算法
可達性分析算法的基本思路就是通過一系列稱爲“GC Roots” 的根對象作爲起始節點集,從這些節點開始根據引用關係向下搜索,搜索走過的路徑稱爲“引用鏈”。如果某個對象到GC Roots 之間沒有任何引用鏈,則證明該對象是不可能被使用的。
可作爲GC Roots的對象如下:
- 在虛擬機棧中引用的對象,比如各個線程被調用方法堆棧中的參數、局部變量、臨時變量等
- 在方法區中類靜態屬性引用的對象,比如引用類型靜態變量
- 在方法區中常量引用的對象,比如字符串常量池裏的引用
- 在本地方法棧中JNI(Native)引用的對象
- Java虛擬機內部的引用,如基本數據類型對應的Class對象,常駐的異常對象
- 所有被同步鎖(synchronized)持有的對象
- 反映虛擬機內部情況的JMXBean、JVMTI中註冊的回調、本地代碼緩存等
除了以上外,也可以"臨時"加入一些對象作爲GC Roots對象。(比如在新生代回收時,就需要考慮老年代對象的調用,需要將關聯區域的對象也一併加入到GC Roots中)
死亡逃脫
先看以下代碼
/**
* 1.對象可以在被GC時自我拯救
* 2.這種自救的機會只有一次,因爲一個對象的finalize()方法最多隻會被系統自動調用一次
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("I am still alive !");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
// 這裏自救,重新建立關聯
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable{
SAVE_HOOK = new FinalizeEscapeGC();
//對象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
// 因爲finalizer 方法優先級很低,暫停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("I am dead!");
}
//下面這段代碼與上面的完全相同,但是這次自救卻失敗了
SAVE_HOOK = null;
System.gc();
// 因爲finalizer 方法優先級很低,暫停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("I am dead!");
}
}
}
執行結果:
這是爲什麼呢?
第一次成功自救是因爲真正對一個對象回收,需要進行兩次標記。
如果對象在進行可達性分析後,發現沒有引用鏈,那它將會被第一次標記,隨後進行一次篩選,篩選的條件是是否有必要執行finalizer 方法,假如對象沒有沒有覆蓋finalizer ()方法,或者finalizer ()已經被調用過,那虛擬機將這兩種情況視爲沒必要執行。第二次不能逃脫,是因爲系統已經執行過finalizer方法了
如果對象被判斷爲有必要執行finalizer()方法,那麼該對象會被放進一個低優先級的隊列F-Queue中,稍後收集器將對F-Queue中的對象進行第二次小規模的標記。第一次逃脫就是因爲在finalizer方法中重新建立了關聯,所以第二次標記時它被移出"即將回收" 的集合。
finalizer方法運行代價高昂,不確定性大,無法保證各個對象的調用順序,已經被官方聲明爲不推薦使用的語法。筆者建議大家使用try-finally方式
回收方法區
方法區的垃圾收集主要回收兩部分內容: 廢棄的常量和不再使用的類型
參考:《深入理解Java虛擬機》