《深入理解Java虛擬機》學(xué)習(xí)筆記之垃圾收集器總結(jié)

垃圾收集(GC)技術(shù)是Java與C++之間的一堵高墻,大部分人可能會認(rèn)為GC是Java語言的伴生產(chǎn)物低千。事實上浪漠,GC的歷史遠(yuǎn)比Java要久遠(yuǎn)硕并,1960年誕生于MIT的Lisp是第一門真正使用內(nèi)存動態(tài)分配和垃圾收集技術(shù)的語言。通常怎炊,要了解GC谭企,其實就是要了解GC需要完成的三件事:

1.哪些內(nèi)存需要回收?

2.什么時候回收评肆?

3.如何回收债查?

目前垃圾收集技術(shù)已經(jīng)相當(dāng)成熟,一切看起來都進入了“自動化”的時代瓜挽,但是當(dāng)我們的系統(tǒng)因為垃圾收集而無法突破瓶頸時盹廷,我們就需要對這些技術(shù)實施必要的監(jiān)控和調(diào)節(jié)。在上一篇的學(xué)習(xí)筆記中可以知道程序計數(shù)器久橙、虛擬機棧和本地方法棧這三個內(nèi)存區(qū)域隨線程而生俄占,隨線程而死(線程私有的歹垫,聲明周期與線程的相同);棧中的棧幀隨著方法的進入和退出有條不紊的執(zhí)行著出棧和入棧操作颠放,因此排惨,這幾個區(qū)域的內(nèi)存分配和回收都具備確定性,不需要過多的考慮碰凶,因為方法結(jié)束或者線程結(jié)束時暮芭,內(nèi)存自然就跟隨著回收了。而Java堆和方法區(qū)則不一樣欲低,這部分的內(nèi)存的分配和回收都是動態(tài)的辕宏,垃圾收集器所關(guān)注的是這部分的內(nèi)存。那么砾莱,接下來我們就對上述所說的GC需要完成的三件事結(jié)合內(nèi)存區(qū)域進行一個總結(jié)瑞筐。

哪些內(nèi)存是需要回收的呢?我們從垃圾收集器關(guān)注的兩個內(nèi)存區(qū)域堆和方法區(qū)說起腊瑟。在Java堆中聚假,存放著幾乎所有的對象實例,垃圾收集器在對堆進行回收前闰非,第一件事就是要確定這些對象之中哪些還“存活”著膘格,哪些已經(jīng)“死去”(即不可能再被任何途徑使用的對象)。判斷對象是否存活的方法有很多财松,其中最常見的一個是引用計數(shù)算法:給對象中添加一個引用計數(shù)器瘪贱,每當(dāng)有一個地方引用它時,計數(shù)器值就加1辆毡;當(dāng)引用失效時菜秦,計數(shù)器值就減1;任何時刻計數(shù)器值為0的對象就是不可能再被使用的舶掖。該方法實現(xiàn)簡單球昨,判定效率也高,例如微軟公司的COM技術(shù)访锻、使用ActionScript3的FlashPlayer褪尝、Python語言等都使用了引用計數(shù)算法進行內(nèi)存管理。但是在主流的Java虛擬機里面卻沒有選用引用計數(shù)算法來管理內(nèi)存期犬,其中最主要的原因就是它很難解決對象之間相互循環(huán)引用的問題河哑,例如對象objA和objB都有字段instance,賦值令objA.instance=objB龟虎,objB.instance=objA璃谨,除此之外這兩個對象再無其他引用,實際上這兩個對象已經(jīng)不可能再被訪問了,但是因為它們相互引用著對方佳吞,導(dǎo)致引用計數(shù)器不為0拱雏,于是引用計數(shù)算法無法通知GC收集器回收它們。

因此在主流的Java虛擬機中底扳,都是通過可達性分析來判斷對象是否存活的(可達性分析算法)铸抑,該算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索衷模,搜索所走過的路徑稱為引用鏈鹊汛,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的阱冶。

說到引用刁憋,就不得不談Java對引用的定義。在jdk1.2以前木蹬,Java中的引用的定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表另外一塊內(nèi)存的起始地址至耻,就稱這塊內(nèi)存代表著一個引用。這種定義很純粹镊叁,但是太過狹隘尘颓,一個對象只能定義為被引用或者沒有被引用兩種狀態(tài),無法滿足我們的需求意系,我們希望引用能夠描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時泥耀,則能保存在內(nèi)存之中饺汹;如果內(nèi)存空間進行垃圾收集后還是非常緊張蛔添,則可以拋棄這些對象。所以在jdk1.2之后兜辞,Java對引用的概念進行了擴充迎瞧,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)逸吵、弱引用(Weak Reference)凶硅、虛引用(Phantom Reference)四種:

1.強引用就是指在程序代碼之中普遍存在的,類似Object obj = new Object()這類的引用扫皱,只要強引用還存在足绅,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。

2.軟引用是用來描述一些還有用但并非必需的對象韩脑。對于軟引用關(guān)聯(lián)著的對象氢妈,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收段多。在jdk1.2之后提供了SoftReference類來實現(xiàn)軟引用首量。

3.弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)送之前加缘。當(dāng)垃圾收集器工作時鸭叙,無論內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象拣宏。在jdk1.2之后提供了WeakReference類來實現(xiàn)弱引用沈贝。

4.虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系勋乾。一個對象是否有虛引用的存在缀程,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例市俊。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被垃圾收集器回收時收到一個系統(tǒng)通知杨凑。在jdk1.2之后提供了PhantomReference類來實現(xiàn)虛引用。

關(guān)于對象的存亡問題摆昧,還有一點值得一說的就是撩满,即使在可達性分析算法中不可達的對象,也并非是“非死不可”的绅你,這時候它們暫時處于緩刑階段伺帘,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈忌锯,那它將會被第一次標(biāo)記并且進行一次篩選伪嫁,篩選的條件是此對象是否有必要執(zhí)行finalize()方法虫蝶。當(dāng)對象沒有覆蓋finalize()方法或者finalize()方法已經(jīng)被虛擬機調(diào)用過围辙,虛擬機將這兩種情況都被視為“沒有必要執(zhí)行”款咖。如果這個對象被判定為有必要執(zhí)行finalize()方法姨拥,那么這個對象將被會放置在一個叫做F-Queue的隊列之中程剥,并在稍后由一個由虛擬機自動建立的笋婿、低優(yōu)先級的Finalizer線程去執(zhí)行它贸伐。這里所謂的“執(zhí)行”是指虛擬機會觸發(fā)這個方法抡草,但并不承諾會等待它運行結(jié)束(防止一個對象在finalize()方法中執(zhí)行緩慢或者發(fā)生了死循環(huán)導(dǎo)致隊列中其他對象處于無休止的等待砚哗,甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰)龙助。finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規(guī)模的標(biāo)記蛛芥,如果對象要在finalize()中成功拯救自己提鸟,只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可,那在第二次標(biāo)記時它將被移除出“即將回收”的集合仅淑。如下面代碼演示結(jié)果:

public class FinalizeEscapeGC {

public static FinalizeEscapeGC SAVE_HOOK = null;

public void isAlive(){

System.out.println("yes, i am still alive!");

}

@Override

protected void finalize() throws Throwable {

// TODO Auto-generated method stub

super.finalize();

System.out.println("finalize method executed!");

FinalizeEscapeGC.SAVE_HOOK = this;

}

public static void main(String[] args) throws Throwable {

// TODO Auto-generated method stub

SAVE_HOOK = new FinalizeEscapeGC();

//對象第一次成功拯救自己

SAVE_HOOK = null;

System.gc();

//因為finalize方法優(yōu)先級很低称勋,所以暫停0.5秒以等待它

Thread.sleep(500);

if(SAVE_HOOK != null){

SAVE_HOOK.isAlive();

}else{

System.out.println("no, i am dead!");

}

//下面這段代碼與上面的完成相同,但是這次卻自救失敗了

SAVE_HOOK = null;

System.gc();

//因為finalize方法優(yōu)先級很低漓糙,所以暫停0.5秒以等待它

Thread.sleep(500);

if(SAVE_HOOK != null){

SAVE_HOOK.isAlive();

}else{

System.out.println("no, i am dead!");

}

}

}

運行結(jié)果:

finalize method executed!

yes, i am still alive!

no, i am dead!

從以上運行結(jié)果可以看出铣缠,SAVE_HOOK對象的finalize()方法確實被GC收集器觸發(fā)過,并且在被收集前成功逃脫了。另外一點要說明的是蝗蛙,任何一個對象的finalize()方法都只會被系統(tǒng)自動調(diào)用一次蝇庭,如果對象面臨下一次回收,它的finalize()方法不會被再次執(zhí)行捡硅,因此第二段代碼的自救行動失敗了哮内。

雖然使用上述方法可以拯救對象,但我們應(yīng)該避免使用它壮韭,因為它不是C/C++中的析構(gòu)函數(shù)北发,它的運行代價高昂,不確定性大喷屋,無法保證各個對象的調(diào)用順序琳拨,finalize()方法是Java剛誕生時為了使C/C++程序員更容易接受它所做的一個妥協(xié)。對于finalize()適合做關(guān)閉外部資源之類工作的說法屯曹,這完全是一種自我安慰狱庇,try-finally或者其它方式都可以做的更好更及時。

垃圾收集器對堆內(nèi)存區(qū)域的回收就總結(jié)到這恶耽,接下來說說對方法區(qū)的回收密任。很多人認(rèn)為方法區(qū)(或者HotSpot虛擬機中的永久代)是沒有垃圾收集的,Java虛擬機規(guī)范中確實說過可以不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集偷俭,而且在方法區(qū)中進行垃圾收集的性價比一般比較低浪讳,在堆中,尤其是在新生代中涌萤,常規(guī)應(yīng)用進行一次垃圾收集一般可以回收70%~95%的空間淹遵,而永久代的垃圾收集效率遠(yuǎn)低于此。永久代的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類形葬『夏牛回收廢棄常量與回收J(rèn)ava堆中的對象非常相似,例如一個字符串“abc”已經(jīng)進入常量池中笙以,但是當(dāng)前系統(tǒng)沒有任何一個String對象是叫做“abc”的,換句話說冻辩,就是沒有任何對象引用常量池中的“abc”常量猖腕,也沒有其他地方引用了這個字面量,如果這時發(fā)生內(nèi)存回收恨闪,而且必要的話倘感,這個“abc”常量就會被系統(tǒng)清理出常量池。常量池中的其它類(接口)咙咽、方法老玛、字段的符號引用也與此類似。

判定一個常量是否是廢棄常量比較簡單,而要判定一個類是否是無用的類的條件則相對苛刻許多蜡豹。類需要同時滿足下面3個條件才能算是無用的類:

1.該類所有的實例都已經(jīng)被回收麸粮,也就是Java堆中不存在該類的任何實例。

2.加載該類的ClassLoader已經(jīng)被回收镜廉。

3.該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用弄诲,無法在任何地方通過反射訪問該類的方法。

虛擬機可以對滿足上述3個條件的無用類進行回收娇唯,這里說的僅僅是可以齐遵,而并不是和對象一樣,不使用了就必然會回收塔插。

以上的就是GC需要回收的內(nèi)存梗摇。什么時候回收也在上面有提及,就不多說了想许,至于如何回收留美,也簡單的介紹一下幾種垃圾收集算法的思想吧。

1.標(biāo)記-清除算法:這是最基礎(chǔ)的收集算法伸刃,如同名字一樣谎砾,算法分為標(biāo)記和清除兩個階段:首先標(biāo)記出所需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象捧颅。之所以說它是最基礎(chǔ)的收集算法景图,是因為接下來要說的收集算法都是基于這種思路并對其不足進行改進而得到的。它的不足主要有兩點:一個是效率問題碉哑,標(biāo)記和清除兩個過程的效率都不高挚币;另一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片扣典,空間碎片太多可能會導(dǎo)致以后在程序運行的過程中需要分配較大對象時妆毕,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。

2.復(fù)制算法:為了解決效率問題而出現(xiàn)的算法贮尖,它將可用內(nèi)存按容量劃分為大小相等的兩塊笛粘,每次只使用其中的一塊,當(dāng)這一塊的內(nèi)存用完了湿硝,就將還存活著的對象復(fù)制到另外一塊上面薪前,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收关斜,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況示括,只要一動堆頂指針,按順序分配內(nèi)存即可痢畜,實現(xiàn)簡單垛膝,運行高效鳍侣。只是這種算法的代價是將內(nèi)存縮小為了原來的一半,未免太高了點吼拥。

3.標(biāo)記-整理算法:復(fù)制收集算法在對象存活率較高時就要進行較多的復(fù)制操作倚聚,效率將會變低。更關(guān)鍵的是扔罪,如果不想浪費50%的空間秉沼,就需要有額外的空間進行分配擔(dān)保,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況矿酵,所以在老年代一般不能直接選用這種算法唬复。根據(jù)老年代的特點,就有了標(biāo)記-整理算法全肮,標(biāo)記過程仍然與標(biāo)記-清理算法一樣敞咧,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動辜腺,然后直接清理掉端邊界以外的內(nèi)存休建。

4.分代收集算法:當(dāng)前商業(yè)虛擬機的垃圾收集都采用分代收集算法,這種算法并沒有什么新的思想评疗,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊测砂。一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適合的收集算法百匆。在新生代中砌些,每次垃圾收集時都會發(fā)現(xiàn)有大批對象死去,只有少量存活加匈,那就選用復(fù)制算法存璃,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因為對象存活效率高雕拼、沒有額外空間對它進行分配擔(dān)保纵东,就必須使用標(biāo)記-清理或者標(biāo)記-整理算法來進行回收。

以上就是對Java虛擬機垃圾收集機制的一些總結(jié)啥寇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末偎球,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子示姿,更是在濱河造成了極大的恐慌甜橱,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栈戳,死亡現(xiàn)場離奇詭異,居然都是意外死亡难裆,警方通過查閱死者的電腦和手機子檀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門镊掖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褂痰,你說我怎么就攤上這事亩进。” “怎么了缩歪?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵归薛,是天一觀的道長。 經(jīng)常有香客問我匪蝙,道長主籍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任逛球,我火速辦了婚禮千元,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颤绕。我一直安慰自己幸海,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布奥务。 她就那樣靜靜地躺著物独,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氯葬。 梳的紋絲不亂的頭發(fā)上挡篓,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音溢谤,去河邊找鬼瞻凤。 笑死,一個胖子當(dāng)著我的面吹牛世杀,可吹牛的內(nèi)容都是我干的阀参。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼瞻坝,長吁一口氣:“原來是場噩夢啊……” “哼蛛壳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起所刀,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衙荐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后浮创,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忧吟,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年斩披,在試婚紗的時候發(fā)現(xiàn)自己被綠了溜族。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讹俊。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖煌抒,靈堂內(nèi)的尸體忽然破棺而出仍劈,到底是詐尸還是另有隱情,我是刑警寧澤寡壮,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布贩疙,位于F島的核電站,受9級特大地震影響况既,放射性物質(zhì)發(fā)生泄漏这溅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一坏挠、第九天 我趴在偏房一處隱蔽的房頂上張望芍躏。 院中可真熱鬧,春花似錦降狠、人聲如沸对竣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽否纬。三九已至,卻和暖如春蛋褥,著一層夾襖步出監(jiān)牢的瞬間临燃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工烙心, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膜廊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓淫茵,卻偏偏與公主長得像爪瓜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匙瘪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

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