Java垃圾回收手冊(cè)(一):初識(shí)垃圾回收

初識(shí)垃圾回收

翻譯原文 => plumbr Java GC handbook

乍一看穿仪,垃圾回收所做的事情應(yīng)當(dāng)恰如其名——查找并清除垃圾。事實(shí)上卻恰恰相反意荤。垃圾回收是用來跟蹤所有仍在使用的對(duì)象啊片,然后將剩余的對(duì)象標(biāo)記為垃圾。牢記了這點(diǎn)之后玖像,我們?cè)賮砀由钊氲亓私庀逻@個(gè)被稱為“垃圾回收”的自動(dòng)化內(nèi)存回收在Java虛擬機(jī)中到底是如何實(shí)現(xiàn)的紫谷。

在介紹細(xì)節(jié)之前,我們從介紹垃圾回收的基本特性捐寥,核心概念和實(shí)現(xiàn)方法等這些基礎(chǔ)知識(shí)開始笤昨。

注意:這些內(nèi)容是基于Oracle的Hotspot和OpenJDK的實(shí)現(xiàn)來介紹的,在其他的其他的運(yùn)行時(shí)或者JVM版本握恳,比如JRockit或者IBM J9上瞒窒,本文所描述有些方面會(huì)完全不適用

手動(dòng)管理內(nèi)存

在介紹現(xiàn)代版的垃圾回收之前,我們先來簡(jiǎn)單地回顧下需要手動(dòng)地顯式分配及釋放內(nèi)存的那些日子乡洼。如果你忘了去釋放內(nèi)存崇裁,那么這塊內(nèi)存就無法重用了匕坯。這塊內(nèi)存被占有了卻沒被使用。這種場(chǎng)景被稱之為內(nèi)存泄露拔稳。

下面是用C寫的一個(gè)手動(dòng)管理內(nèi)存的簡(jiǎn)單例子:

int send_request() {
    size_t n = read_size();
    int *elements = malloc(n * sizeof(int));

    if(read_elements(n, elements) < n) {
        // elements not freed!
        return -1;
    }

    // …

    free(elements)
    return 0;
}

有過C語言經(jīng)驗(yàn)的人可以深刻的體會(huì)到葛峻,你很容易就會(huì)忘了釋放內(nèi)存。內(nèi)存泄露曾經(jīng)是個(gè)非常普遍的問題巴比。你只能通過不斷地修復(fù)自己的代碼來與它們進(jìn)行抗?fàn)幣⑶浮R虼耍枰幸环N更優(yōu)雅的方式來自動(dòng)釋放無用內(nèi)存匿辩,從而消除人為錯(cuò)誤的可能性腰耙。這種自動(dòng)化過程被稱為垃圾回收(簡(jiǎn)稱GC)。

智能指針

自動(dòng)垃圾回收早期的一種實(shí)現(xiàn)便是通過析構(gòu)器铲球。例如挺庞,我們?cè)贑++里面可以通過使用vector來做同樣的事情,當(dāng)vector對(duì)象離開作用域時(shí)稼病,vector的析構(gòu)器會(huì)被自動(dòng)調(diào)用以回收內(nèi)存选侨。

int send_request() {
    size_t n = read_size();
    vector<int> elements = vector<int>(n);

    if(read_elements(elements.size(), &elements[0]) < n) {
        return -1;
    }

    return 0;
}

但是在更復(fù)雜的情況下,特別是在多線程之間共享對(duì)象時(shí)然走,僅僅依賴析構(gòu)器是無法實(shí)現(xiàn)自動(dòng)內(nèi)存回收的援制。這個(gè)時(shí)候,引用計(jì)數(shù)技術(shù)作為最簡(jiǎn)單的垃圾回收器應(yīng)運(yùn)而生芍瑞。對(duì)于每個(gè)對(duì)象晨仑,你知曉它被引用了幾次,當(dāng)計(jì)數(shù)器歸零時(shí)拆檬,這個(gè)對(duì)象就可以被安全地回收掉了洪己。C++的共享指針就是一個(gè)非常著名的例子:

int send_request() {
    size_t n = read_size();
    auto elements = make_shared<vector<int>>();

    // read elements

    store_in_cache(elements);

    // process elements further

    return 0;
}

現(xiàn)在,為了避免在下次該函數(shù)被調(diào)用時(shí)竟贯,重新讀取元素答捕,我們可能希望把這些元素緩存起來。在這種情況下屑那,當(dāng)該vector對(duì)象離開作用域時(shí)是不能銷毀該vector對(duì)象的拱镐。因此我們可以使用共享指針,它會(huì)記錄這個(gè)對(duì)象被引用的次數(shù)持际。如果你將它傳遞給別人則計(jì)數(shù)加一沃琅,當(dāng)它離開了作用域后便會(huì)減一。一旦這個(gè)計(jì)數(shù)為零选酗,共享指針會(huì)自動(dòng)地刪除底層對(duì)應(yīng)的vector阵难。

自動(dòng)內(nèi)存管理

在上面的C++代碼中,我們還得顯式地聲明我們需要使用內(nèi)存管理芒填。那如果所有的對(duì)象都采用這個(gè)機(jī)制會(huì)怎樣呢呜叫?那簡(jiǎn)直就太方便了空繁,這樣開發(fā)人員便無需考慮清理內(nèi)存的事情了。運(yùn)行時(shí)會(huì)自動(dòng)知曉哪些內(nèi)存不再使用了朱庆,然后釋放掉它盛泡。也就是說,它自動(dòng)地回收了這些垃圾娱颊。第一代的垃圾回收器是1959年Lisp引入的傲诵,這項(xiàng)技術(shù)迄今為止一直在不斷演進(jìn)。

引用計(jì)數(shù)

剛才我們用C++的共享指針?biāo)菔镜南敕梢詰?yīng)用到所有的對(duì)象上來箱硕。許多語言比如說Perl, Python以及PHP拴竹,采用的都是這種方式。這個(gè)通過一張圖可以很容易說明:

Java-GC-counting-references1.png

綠色的云所指向的對(duì)象表示仍然被程序使用剧罩。從技術(shù)層面上來說栓拜,這有點(diǎn)像是正在執(zhí)行的某個(gè)方法里面的局部變量,亦或是靜態(tài)變量之類的惠昔。不同編程語言的情況可能會(huì)不一樣幕与,因此這并不是我們關(guān)注的重點(diǎn)。

藍(lán)色的圓圈代表的是內(nèi)存中的活著的對(duì)象镇防,可以看到有多少對(duì)象引用了它們啦鸣。灰色圓圈的對(duì)象是已經(jīng)沒有任何人引用的了来氧。因此诫给,它們屬于垃圾對(duì)象,可以被垃圾回收器清理掉饲漾。

看起來還不錯(cuò)對(duì)吧蝙搔?沒錯(cuò),不過這里存在著一個(gè)重大的缺陷考传。很容易會(huì)出現(xiàn)一些孤立的環(huán),它們中的對(duì)象都不在任何域內(nèi)证鸥,但彼此卻互相引用導(dǎo)致引用數(shù)不為0僚楞。下面便是一個(gè)例子:

Java-GC-cyclical-dependencies.png

看到了吧,紅色部分其實(shí)就是應(yīng)用程序不再使用的垃圾對(duì)象枉层。由于引用計(jì)數(shù)的缺陷泉褐,因此會(huì)存在內(nèi)存泄露。

有幾種方法可以解決這一問題鸟蜡,比如說使用特殊的“弱”引用膜赃,或者使用一個(gè)單獨(dú)的算法回收循環(huán)引用。之前提到的Perl,Python以及PHP等語言揉忘,都是使用類似的方法來回收循環(huán)引用的跳座,不過這已經(jīng)超出本文講述的范圍了端铛。我們準(zhǔn)備詳細(xì)介紹下JVM所采用的方法。

標(biāo)記刪除

首先疲眷,JVM對(duì)于對(duì)象可達(dá)性的定義要明確一些禾蚕。它可不像前面那樣用綠色的云便含糊了事的,而是有著非常明確及具體的垃圾回收根對(duì)象(Garbage Collection Roots)的定義:

  • 局部變量
  • 活動(dòng)線程
  • 靜態(tài)字段
  • JNI引用
  • 其它(后面將會(huì)討論到)

JVM通過標(biāo)記刪除的算法來記錄所有可達(dá)(存活)對(duì)象狂丝,同時(shí)確保不可達(dá)對(duì)象的那些內(nèi)存能夠被重用换淆。這包含兩個(gè)步驟:

  • 標(biāo)記是指遍歷所有可達(dá)對(duì)象,然后在本地內(nèi)存中記錄這些對(duì)象的信息
  • 刪除會(huì)確保不可達(dá)對(duì)象的內(nèi)存地址可以在下一次內(nèi)存分配中使用几颜。

JVM中的不同GC算法倍试,比如說Parallel Scavenge,Parallel Mark+Copy蛋哭, CMS都是這一算法的不同實(shí)現(xiàn)县习,只是各階段略有不同而已,從概念上來講仍然是對(duì)應(yīng)著上面所說的那兩個(gè)步驟具壮。

這種實(shí)現(xiàn)最重要的就是不會(huì)再出現(xiàn)泄露的對(duì)象環(huán)了:

Java-GC-mark-and-sweep.png

缺點(diǎn)就是應(yīng)用程序的線程需要被暫停才能完成回收准颓,如果引用一直在變的話你是無法進(jìn)行計(jì)數(shù)的。這個(gè)應(yīng)用程序被暫停以便JVM可以進(jìn)行整理活動(dòng)的情況又被稱為Stop The World pause(STW)棺妓。這種暫停被觸發(fā)的可能性有很多攘已,不過垃圾回收應(yīng)該是最常見的一種。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怜跑,一起剝皮案震驚了整個(gè)濱河市样勃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌性芬,老刑警劉巖峡眶,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異植锉,居然都是意外死亡辫樱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門俊庇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狮暑,“玉大人,你說我怎么就攤上這事辉饱“崮校” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵彭沼,是天一觀的道長(zhǎng)缔逛。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么褐奴? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任按脚,我火速辦了婚禮,結(jié)果婚禮上歉糜,老公的妹妹穿的比我還像新娘乘寒。我一直安慰自己,他們只是感情好匪补,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布伞辛。 她就那樣靜靜地躺著,像睡著了一般夯缺。 火紅的嫁衣襯著肌膚如雪蚤氏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天踊兜,我揣著相機(jī)與錄音竿滨,去河邊找鬼。 笑死捏境,一個(gè)胖子當(dāng)著我的面吹牛于游,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垫言,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贰剥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了筷频?” 一聲冷哼從身側(cè)響起蚌成,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凛捏,沒想到半個(gè)月后担忧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坯癣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年瓶盛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片示罗。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓬网,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹉勒,到底是詐尸還是另有隱情,我是刑警寧澤吵取,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布禽额,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脯倒。R本人自食惡果不足惜实辑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藻丢。 院中可真熱鬧剪撬,春花似錦、人聲如沸悠反。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斋否。三九已至梨水,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茵臭,已是汗流浹背疫诽。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旦委,地道東北人奇徒。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缨硝,于是被迫代替她去往敵國和親摩钙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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