一、什么是GC
GC(Garbage Collecor)是JVM的內(nèi)存回收器啤咽,當應用使用的內(nèi)存不足時冰悠,會導致OOM(Out-Of-Memory)。
Java提供的GC可以自動監(jiān)測對象是否超過作用域從而達到自動回收內(nèi)存的目的(Java沒有提供主動釋放已分配內(nèi)存的方法)搀庶。
Java的GC會自動管理內(nèi)存,如果要主動請求內(nèi)存回收铜异,可以調(diào)用以下方法:
- System.gc()
- Runtime.getRuntime().gc()
二哥倔、GC如何管理內(nèi)存(GC算法)
當我們創(chuàng)建對象時,JVM就會為我們創(chuàng)建的對象分配一塊內(nèi)存揍庄,那么這塊內(nèi)存GC如何管理呢咆蒿?
引用計數(shù)法(該算法無法解決循環(huán)引用的情況,導致內(nèi)存無法釋放蚂子,GC已不使用該方法):
- 對象創(chuàng)建時沃测,初始化計數(shù)為1;
- 每當有一個地方引用它食茎,計數(shù)就+1蒂破;
- 每當有一個地方引用失效時,計數(shù)就-1别渔;
可達性分析法(GC目前彩該方法):
該方法的基本思想是通過一系列稱為“GC Roots”的對象作為起點附迷,從這些點向下搜索惧互,搜索走過的路徑稱為“引用鏈”,當某個對象到GC Roots沒有任何引用鏈(即GC Roots不可達至該對象)時喇伯,則證明該對象是不可用的喊儡,就可以回收該對象內(nèi)存。
其它算法(會在以后分別分析)
三稻据、GC Roots
如何選擇GC Roots呢艾猜?在Java中,可以作為GC Roots的包括以下幾種:
- 系統(tǒng)類加載器(bottstrap)加載的類捻悯;
- JVM虛擬機棧(棧幀中的局部變量區(qū)匆赃,也叫做局部變量表)中引用對象;
- JVM方法區(qū)中的類靜態(tài)屬性引用的對象秋度;
- JVM常量池中引用的對象炸庞;
- JVM本地方法棧中JNI(Native方法)引用的對象;
- 活動著的線程
下面給出一個GC Roots的圖例:
上圖為GC Roots的引用鏈荚斯,obj8, obj9, obj10都沒有到GC Roots對象的引用鏈埠居,因此會被回收。
四事期、對象引用以及基于可達性分析的內(nèi)存回收原理
對于可達性分析算法而言滥壕,未到達的對象并非是“非死不可”的,若要宣判一個對象的死亡兽泣,至少需要經(jīng)歷兩次標記階段:
- 如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連的引用鏈绎橘,則對該對象進行第一次標記并進行一次篩選,篩選的條件為:
是否有必要執(zhí)行該對象的finalize方法
- 若對象沒有覆蓋finalize方法或者該finalize方法已經(jīng)被JVM執(zhí)行過了唠倦,則均視為不必要執(zhí)行對象的finalize方法称鳞,即該對象將會被回收;
- 若對象覆蓋了finalize方法且沒有被執(zhí)行過稠鼻,則該對象會被放置在一個叫F-Queue的隊列中冈止,之后會由JVM自動建立優(yōu)先級低的Finalizer線程去執(zhí)行,而JVM不需要等待該線程執(zhí)行結束候齿,即JVM只負責建立線程熙暴,其它事則交由該線程去處理;
- 對F-Queue中的對象進行第二次標記:
- 如果對象在finalize方法中拯救了自己慌盯,即關聯(lián)上了GC Roots引用鏈(如把this關鍵字賦值給其它變量)周霉,那么在第二次標記時該對象將從“即將回收”的集合中移除;
- 如果對象沒有拯救自己亚皂,那么就會被回收俱箱;
以下代碼演示了對象如何在finalize方法中拯救自己(然后,它只能自救一次灭必,第二次仍舊被回收):
public class GC {
public static GC SAVE_HOOK = null;
public static void main(String[] args) throws InterruptedException {
// 新建對象匠楚,因為SAVE_HOOK指向這個對象巍膘,對象此時的狀態(tài)是(reachable,unfinalized)
SAVE_HOOK = new GC();
// 將SAVE_HOOK設置成null,此時剛才創(chuàng)建的對象就不可達了芋簿,因為沒有句柄再指向它了,
// 對象此時狀態(tài)是(unreachable璃饱,unfinalized)
SAVE_HOOK = null;
// 強制系統(tǒng)執(zhí)行垃圾回收与斤,系統(tǒng)發(fā)現(xiàn)剛才創(chuàng)建的對象處于unreachable狀態(tài),
// 并檢測到這個對象的類覆蓋了finalize方法荚恶,因此把這個對象放入F-Queue隊列撩穿,
// 由低優(yōu)先級線程執(zhí)行它的finalize方法,此時對象的狀態(tài)變成(unreachable, finalizable)
// 或者是(finalizer-reachable,finalizable)
System.gc();
// sleep谒撼,目的是給低優(yōu)先級線程從F-Queue隊列取出對象并執(zhí)行其finalize方法提供機會食寡。
// 在執(zhí)行完對象的finalize方法中的super.finalize()時,對象的狀態(tài)變成(unreachable,finalized)狀態(tài)廓潜,
// 但接下來在finalize方法中又執(zhí)行了SAVE_HOOK = this;這句話抵皱,又有句柄指向這個對象了,對象又可達了辩蛋。
// 因此對象的狀態(tài)又變成了(reachable, finalized)狀態(tài)呻畸。
Thread.sleep(500);
// 這里樓主說對象處于(reachable,finalized)狀態(tài)應該是合理的。對象的finalized方法被執(zhí)行了悼院,
// 因此是finalized狀態(tài)伤为。又因為在finalize方法是執(zhí)行了SAVE_HOOK=this這句話,本來是unreachable的對象据途,
// 又變成reachable了绞愚。
if (null != SAVE_HOOK) { //此時對象應該處于(reachable, finalized)狀態(tài)
// 這句話會輸出,注意對象由unreachable颖医,經(jīng)過finalize復活了位衩。
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
// 再一次將SAVE_HOOK放空,此時剛才復活的對象便脊,狀態(tài)變成(unreachable,finalized)
SAVE_HOOK = null;
// 再一次強制系統(tǒng)回收垃圾蚂四,此時系統(tǒng)發(fā)現(xiàn)對象不可達,雖然覆蓋了finalize方法哪痰,但已經(jīng)執(zhí)行過了遂赠,因此直接回收。
System.gc();
// 為系統(tǒng)回收垃圾提供機會
Thread.sleep(500);
if (null != SAVE_HOOK) {
// 這句話不會輸出晌杰,因為對象已經(jīng)徹底消失了跷睦。
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
// 這句話讓對象的狀態(tài)由unreachable變成reachable,就是對象復活
SAVE_HOOK = this;
}
}
運行結果如下:
finalize method executed!
yes, i am still alive
no, i am dead
由此可見肋演,該對象只能拯救自己一次抑诸,因為對象的finalize方法最多被JVM調(diào)用一次烂琴。
此外,我們還可以得知蜕乡,一個堆對象的this(放在局部變量表中的第一項)引用會永遠存在奸绷,在方法體內(nèi)可以將this引用賦值給其它變量,這樣堆中對象就可以被其它變量所引用层玲,即不會被回收号醉。
五、方法區(qū)的垃圾回收
- 方法區(qū)的垃圾回收主要回收兩部分內(nèi)容:
- 廢棄常量辛块;
- 無用的類畔派;
既然進行垃圾回收,就需要判斷哪些是廢棄常量润绵,哪些是無用的類线椰?
如何判斷廢棄常量呢?以字面量回收為例尘盼,如果一個字符串“abc”已經(jīng)進入常量池憨愉,但是當前系統(tǒng)沒有任何一個String對象引用了叫做“abc”的字面量,那么悔叽,如果發(fā)生垃圾回收并且有必要時她混,“abc”就會被系統(tǒng)移出常量池蔚约。常量池中的其他類(接口)枢泰、方法凶朗、字段的符號引用也與此類似。
如何判斷無用的類呢趟庄?需要滿足以下三個條件:
- 該類的所有實例都已經(jīng)被回收括细,即Java堆中不存在該類的任何實例;
- 加載該類的ClassLoader已經(jīng)被回收戚啥;
- 該類對應的java.lang.Class對象沒有在任何地方被引用奋单,無法在任何地方通過反射訪問該類的方法;