Java GC系列一:原理

一、什么是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.png

上圖為GC Roots的引用鏈荚斯,obj8, obj9, obj10都沒有到GC Roots對象的引用鏈埠居,因此會被回收。

四事期、對象引用以及基于可達性分析的內(nèi)存回收原理

references.png

對于可達性分析算法而言滥壕,未到達的對象并非是“非死不可”的,若要宣判一個對象的死亡兽泣,至少需要經(jīng)歷兩次標記階段:

  1. 如果對象在進行可達性分析后發(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只負責建立線程熙暴,其它事則交由該線程去處理;
  1. 對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ū)的垃圾回收

  1. 方法區(qū)的垃圾回收主要回收兩部分內(nèi)容:
  • 廢棄常量辛块;
  • 無用的類畔派;

既然進行垃圾回收,就需要判斷哪些是廢棄常量润绵,哪些是無用的類线椰?

如何判斷廢棄常量呢?以字面量回收為例尘盼,如果一個字符串“abc”已經(jīng)進入常量池憨愉,但是當前系統(tǒng)沒有任何一個String對象引用了叫做“abc”的字面量,那么悔叽,如果發(fā)生垃圾回收并且有必要時她混,“abc”就會被系統(tǒng)移出常量池蔚约。常量池中的其他類(接口)枢泰、方法凶朗、字段的符號引用也與此類似。

如何判斷無用的類呢趟庄?需要滿足以下三個條件:

  • 該類的所有實例都已經(jīng)被回收括细,即Java堆中不存在該類的任何實例;
  • 加載該類的ClassLoader已經(jīng)被回收戚啥;
  • 該類對應的java.lang.Class對象沒有在任何地方被引用奋单,無法在任何地方通過反射訪問該類的方法;
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猫十,一起剝皮案震驚了整個濱河市览濒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拖云,老刑警劉巖贷笛,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宙项,居然都是意外死亡乏苦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汇荐,“玉大人洞就,你說我怎么就攤上這事∠铺裕” “怎么了旬蟋?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長革娄。 經(jīng)常有香客問我咖为,道長,這世上最難降的妖魔是什么稠腊? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮鸣哀,結果婚禮上架忌,老公的妹妹穿的比我還像新娘。我一直安慰自己我衬,他們只是感情好叹放,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挠羔,像睡著了一般井仰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上破加,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天俱恶,我揣著相機與錄音,去河邊找鬼范舀。 笑死合是,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的锭环。 我是一名探鬼主播聪全,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辅辩!你這毒婦竟也來了难礼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤玫锋,失蹤者是張志新(化名)和其女友劉穎蛾茉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體景醇,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡臀稚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了三痰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吧寺。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡窜管,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稚机,到底是詐尸還是另有隱情幕帆,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布赖条,位于F島的核電站失乾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏纬乍。R本人自食惡果不足惜碱茁,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仿贬。 院中可真熱鬧纽竣,春花似錦、人聲如沸茧泪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽队伟。三九已至穴吹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嗜侮,已是汗流浹背港令。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棘钞,地道東北人缠借。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像宜猜,于是被迫代替她去往敵國和親泼返。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容