5. 垃圾收集

前言

談起JVM, 那么就不得不提垃圾收集(Garbage Collection 通常被稱為“GC”).

什么是垃圾收集呢?

想解答這個問題, 我們最好將問題拆解開

  • 如何確定垃圾?
  • 如何回收垃圾?
  • 何時回收垃圾?

下面圍繞這三件事, 我們站在JVM層面梳理下垃圾收集的機(jī)制.

如何確定垃圾?

從JVM層面來看, 它管理的是生命周期內(nèi)的全部實例對象, 那么所謂的垃圾其實就是“無用的對象”.

那么它是如何確定“無用對象”的?

引用計數(shù)法

在 Java 中,引用和對象是有關(guān)聯(lián)的, 必須使用引用來操作對象.

People zs = new People();
zs.setName("張三");
zs是個引用, 和真正的對象“new People()”關(guān)聯(lián)

因此, 簡單的辦法是通過引用計數(shù)來判斷一個對象是否可以回收.

所以在JVM中, 每個對象都在對象頭結(jié)構(gòu)中維護(hù)了一個引用計數(shù)屬性

  • 對象被引用一次時, 計數(shù)就加1
  • 對象的引用被釋放時,計數(shù)就減1
  • 對象的計數(shù)為0的時, 這個對象就可以被回收了

引用計數(shù)法聽著雖然簡單易懂, 判定效率也高.
但是,當(dāng)前主流的虛擬機(jī)都沒有采用這個算法來管理內(nèi)存,其中最主要的原因是它很難解決對象之間互相循環(huán)引用的問題.

循環(huán)引用問題

所謂對象之間互相循環(huán)引用,如下面代碼所示:
除了對象 objA 和 objB 相互引用著對方之外,這兩個對象之間再無任何引用.
但是它們因為互相引用對方,導(dǎo)致它們的引用計數(shù)器都不為 0,于是引用計數(shù)算法無法通知 GC 回收器回收他們.

PS: 實際上以下示例是能回收的, 因為JVM沒有采用引用計數(shù)法

public class ReferenceCountingGc {
    public Object instance = null;

    public static void main(String[] args) {
        ReferenceCountingGc obj1 = new ReferenceCountingGc();
        ReferenceCountingGc obj2 = new ReferenceCountingGc();
        obj1.instance = obj2;
        obj2.instance = obj1;
        obj1 = null;
        obj2 = null;
    }
}

可達(dá)性分析

為了解決引用計數(shù)法的循環(huán)引用問題, Java 使用了可達(dá)性分析的方法.

可達(dá)性分析就是通過一系列的稱為 “GC Roots”的對象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,節(jié)點(diǎn)所走過的路徑稱為引用鏈.
當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的.

如下圖中的 Object 6 ~ Object 10 之間雖有引用關(guān)系,但它們到 GC Roots 不可達(dá), 因此為需要被回收的對象.

image.png

在Java中, GC Roots包括:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧(Native 方法)中引用的對象
  • 方法區(qū)中類靜態(tài)屬性引用的對象
  • 方法區(qū)中常量引用的對象
  • 所有被同步鎖持有的對象

要注意的是:

  • 不可達(dá)對象不等價于可回收對象
  • 不可達(dá)對象變?yōu)榭苫厥諏ο笾辽僖?jīng)過兩次標(biāo)記過程,兩次標(biāo)記后仍然是可回收對象,則將面臨回收

如何回收垃圾?

確定垃圾, 那么如何回收呢?
這就不得不談一系列的垃圾回收算法, 算法實現(xiàn)會因各個平臺虛擬機(jī)的差異而不同, 這里我們只談幾種主流的算法思想.

標(biāo)記-清除算法 (Mark-Sweep)

這是最基礎(chǔ)的收集算法, 分為標(biāo)記、清除兩個階段
主要算法思想就是

通過算法標(biāo)記出回收對象(舉例: hotspot使用可達(dá)性分析),進(jìn)而回收標(biāo)記的對象占用的空間

image.png

從示例圖不難看出, 該算法的最大缺陷就

  • 內(nèi)存碎片化

后續(xù)碰到分配大對象時(連續(xù)的內(nèi)存空間), 必然導(dǎo)致內(nèi)存不夠從而觸發(fā)額外的GC.

另外就是標(biāo)記和清除兩個過程本身的效率都不高.

標(biāo)記-復(fù)制算法(copying)

復(fù)制算法算是Mark-Sweep算法的升級版, 主要就是為了解決Mark-Sweep算法“內(nèi)存碎片化”的問題.

該算法的主要思想如下

按內(nèi)存容量將內(nèi)存劃分為等大小的兩塊. 每次只使用其中一塊,當(dāng)這一塊內(nèi)存滿后將尚存活的對象復(fù)制到另一塊上去, 把已使用的內(nèi)存清掉.

image.png

這種算法雖然實現(xiàn)簡單,內(nèi)存效率高,不易產(chǎn)生碎片,但是存在兩個較嚴(yán)重問題

  • 可用內(nèi)存被壓縮到了原來的一半

  • 存活對象較多的話, Copying算法的效率較低

標(biāo)記-整理算法(Mark-Compact)

為了解決以上兩種算法的缺陷, 進(jìn)而提出了標(biāo)記整理算法.

算法的主要思想如下

分為標(biāo)記也拜、整理兩個階段
標(biāo)記階段和Mark-Sweep相同, 不同點(diǎn)是標(biāo)記后不會清理對象, 而是將存活對象移向內(nèi)存的一端.
然后清除端邊界外的對象.


image.png

分代收集算法

上面介紹了幾個算法都各有優(yōu)缺點(diǎn), 但沒有哪個是絕對優(yōu)勢的.
只能說每個算法都有各自的應(yīng)用場景.

而在JVM垃圾回收領(lǐng)域, 面對各種內(nèi)存回收的復(fù)雜場景, 顯然, 不可能存在一種算法就能達(dá)到最優(yōu)解.

此時聰明的開發(fā)者就提出了一種想法, 既然無法“一招通殺”, 那么, 我就“分而治之”.

通過一定規(guī)則把內(nèi)存區(qū)域劃成幾塊, 這樣某些小塊的內(nèi)存回收場景就存在某個“最優(yōu)解回收算法”, 每塊都是最優(yōu)解, 那么總體上不就是最優(yōu)解么?!

于是, 分代收集算法應(yīng)運(yùn)而生.

嚴(yán)格來說, 分代收集算法并不是個垃圾回收算法, 而是把對象按生命周期來進(jìn)行內(nèi)存劃分的思想.

該算法的主要思想如下

根據(jù)對象存活的不同生命周期, 將內(nèi)存劃分為幾塊不同的區(qū)域.
一般情況下將Java堆劃分為新生代和老年代

  • 新生代的對象特點(diǎn)是大部分對象都是朝生夕死,生命周期很短, 每次垃圾回收時有大量對象需要被回收

  • 老年代的對象特點(diǎn)是生命周期較長,每次垃圾回收時只有少量對象需要被回收

結(jié)合新生代崎弃、老年代的特點(diǎn), 于是適配了合適的垃圾回收算法

新生代與復(fù)制算法

目前大部分JVM 的 GC 對于新生代都采取 Copying 算法,

因為新生代每次垃圾回收都要回收大部分死亡對象,存活的對象少, 所以要復(fù)制的操作比較少.
這樣的特點(diǎn)剛好能發(fā)揮Copying 算法的效率.

新生代的劃分并沒有嚴(yán)格按Copying 算法的1:1劃分法, 而是將新生代劃分為一塊較大的 Eden區(qū)和兩個較小的 Survivor區(qū)(From區(qū), To區(qū))(一般也稱為S1和S2區(qū)),
默認(rèn)內(nèi)存占比為 Eden:S1:S2 是8:1:1

image.png

每次使用Eden區(qū)和其中的一塊 Survivor 區(qū),當(dāng)進(jìn)行垃圾回收時,將該兩塊區(qū)中還存活的對象復(fù)制到另一塊 Survivor區(qū)中.

老年代與標(biāo)記整理算法

老年代本身存放的對象都是熬過了一輪輪GC的, 都是“存活幾率”較高的, 老年代最終存放著大量的對象, 所以每次只需對少量死亡對象進(jìn)行回收, 因而采用 Mark-Compact 算法.

一次完整的GC過程如下

實例理解:

  • 新New的對象一般出現(xiàn)在Eden區(qū)

    • PS: 少數(shù)大對象(需要連續(xù)的內(nèi)存空間) 會直接進(jìn)老年代
    • PS: Hotspot可配置: -XX:PretenureSizeThreshold=2m , 即2m以上的對象直接進(jìn)老年代
  • 慢慢的Eden區(qū)滿了, 此時觸發(fā)一次GC, 將還存活的對象復(fù)制到某個空的S區(qū), 稱為S1區(qū)

    • PS: S1和S2身份隨時互換, 只有空的我們稱為S1區(qū), 兩個S區(qū)必然有一個是空的
    • PS: 也就是假設(shè)年輕代空間比例8:1:1
  • 慢慢的S1區(qū)也滿了, 此時觸發(fā)GC, 已滿的S1區(qū)和Eden區(qū)還存活的對象

  • 對象的內(nèi)存分配主要在新生代的 Eden區(qū)和 From區(qū), 少數(shù)情況(比如new了個大對象, 新生代放不下了)會直接分配到老生代

  • 當(dāng)新生代的 Eden Space 和 From Space 空間不足時就會發(fā)生一次 GC揪利,進(jìn)行 GC 后雹嗦,Eden Space 和 From Space 區(qū)的存活對象會被挪到 To Space祭往,然后將 Eden Space 和 From Space 進(jìn)行清理挡爵。
  • 如果 To Space 無法足夠存儲某個對象荡含,則將這個對象存儲到老生代咒唆。
  • 在進(jìn)行 GC 后,使用的便是 Eden Space 和 To Space 了释液,如此反復(fù)循環(huán)全释。
  • 當(dāng)對象在 Survivor 區(qū)躲過一次 GC 后,其年齡就會+1误债。默認(rèn)情況下年齡到達(dá) 15 的對象會被 移到老生代中浸船。

請關(guān)注我的訂閱號

訂閱號.png

參考

  • 《深入理解JAVA虛擬機(jī):JVM高級特性與最佳實踐》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妄迁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子李命,更是在濱河造成了極大的恐慌登淘,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件封字,死亡現(xiàn)場離奇詭異黔州,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阔籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門流妻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笆制,你說我怎么就攤上這事绅这。” “怎么了在辆?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵证薇,是天一觀的道長。 經(jīng)常有香客問我匆篓,道長浑度,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任奕删,我火速辦了婚禮,結(jié)果婚禮上疗认,老公的妹妹穿的比我還像新娘完残。我一直安慰自己,他們只是感情好横漏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布谨设。 她就那樣靜靜地躺著,像睡著了一般缎浇。 火紅的嫁衣襯著肌膚如雪扎拣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天素跺,我揣著相機(jī)與錄音二蓝,去河邊找鬼。 笑死指厌,一個胖子當(dāng)著我的面吹牛刊愚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播踩验,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鸥诽,長吁一口氣:“原來是場噩夢啊……” “哼商玫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牡借,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤拳昌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钠龙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炬藤,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年俊鱼,在試婚紗的時候發(fā)現(xiàn)自己被綠了刻像。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡并闲,死狀恐怖细睡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帝火,我是刑警寧澤溜徙,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站犀填,受9級特大地震影響蠢壹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜九巡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一图贸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冕广,春花似錦疏日、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至睬辐,卻和暖如春挠阁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背溯饵。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工侵俗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丰刊。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓坡慌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藻三。 傳聞我的和親對象是個殘疾皇子洪橘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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