精華推薦 | 【JVM深層系列】「GC底層調(diào)優(yōu)系列」一文帶你徹底加強夯實底層原理之GC垃圾回收技術(shù)的分析指南(GC原理透析)

前提介紹

很多小伙伴赋元,都跟我反饋忘蟹,說自己總是對JVM這一塊的學(xué)習(xí)和認識不夠扎實也不夠成熟,因為JVM的一些特性以及運作機制總是混淆以及不確定搁凸,導(dǎo)致面試和工作實戰(zhàn)中出現(xiàn)了很多的紕漏和短板媚值,解決廣大小伙伴痛點,我寫了本篇文章护糖,希望可以幫助大家夯實基礎(chǔ)和鍛造JVM技術(shù)功底褥芒。

什么是垃圾收集(GC)

在JVM領(lǐng)域中GC(Garbage Collection)翻譯為 “垃圾收集“,Garbage Collector翻譯為 “垃圾收集器”嫡良。

分代模型(Generational Model)

我們都知道在JVM中锰扶,執(zhí)行垃圾收集需要停止整個應(yīng)用(STW)。對象越多則收集所有垃圾消耗的時間就越長寝受。程序中的大多數(shù)可回收的內(nèi)存可歸為兩類:

  1. 大部分對象很快就不再使用
  2. 還有一部分不會立即無用坷牛,但也不會持續(xù)(太)長時間

這形成了分代數(shù)據(jù)模型『艹危基于這一結(jié)構(gòu), VM中的內(nèi)存被分為年輕代(Young Generation)和老年代(Old Generation)京闰,老年代有時候也稱為年老區(qū)(Tenured)。如下所示甩苛。

image

從上圖可以看出拆分為這樣兩個可清理的單獨區(qū)域蹂楣,允許采用不同的算法來大幅提高GC的性能。

分代模型出現(xiàn)問題

在不同分代中的對象可能會互相引用, 在收集某一個分代時就會成為 “事實上的” GC root浪藻。當(dāng)然捐迫,要著重強調(diào)的是,分代假設(shè)并不適用于所有程序爱葵。

分代模型適合場景

GC算法專門針對“總體生命周期較短”施戴,“總體生命周期較長” 這類特征的對象來進行優(yōu)化, JVM對收集那種存活時間半長不長的對象就顯得非常尷尬了反浓,如下圖對象分布。

image

堆內(nèi)存中的內(nèi)存池劃分也是類似的赞哗。不太容易理解的地方在于各個內(nèi)存池中的垃圾收集是如何運行的雷则。

image

新生代(Eden,伊甸園)

Eden是內(nèi)存中的一個區(qū)域, 用來分配新創(chuàng)建的對象肪笋。通常會有多個線程同時創(chuàng)建多個對象月劈,所以Eden區(qū)被劃分為多個線程本地分配緩沖區(qū)(Thread Local Allocation Buffer, 簡稱TLAB)。通過這種緩沖區(qū)劃分藤乙,大部分對象直接由JVM 在對應(yīng)線程的TLAB中分配, 避免與其他線程的同步操作猜揪。

image

如果 TLAB 中沒有足夠的內(nèi)存空間, 就會在共享Eden區(qū)(shared Eden space)之中分配。如果共享Eden區(qū)也沒有足夠的空間, 就會觸發(fā)一次 年輕代GC 來釋放內(nèi)存空間坛梁。如果GC之后 Eden 區(qū)依然沒有足夠的空閑內(nèi)存區(qū)域, 則對象就會被分配到老年代空間(Old Generation)而姐。

image

當(dāng)Eden區(qū)進行垃圾收集時,GC將所有從root可達的對象過一遍, 并標(biāo)記為存活對象划咐。

image

對象間可能會有跨代的引用拴念,所以需要一種方法來標(biāo)記從其他分代中指向Eden的所有引用。這樣做又會遭遇各個分代之間一遍又一遍的引用褐缠。JVM在實現(xiàn)時采用了卡片標(biāo)記(card-marking)政鼠。

卡片標(biāo)記

JVM只需要記住Eden區(qū)中 “臟”對象的粗略位置,可能有老年代的對象引用指向這部分區(qū)間队魏。

存活區(qū)(Survivor Spaces)

Eden區(qū)的旁邊是兩個存活區(qū), 稱為 from 空間和 to 空間公般。需要著重強調(diào)的的是, 任意時刻總有一個存活區(qū)是空的(empty)。

image

空的那個存活區(qū)用于在下一次年輕代GC時存放收集的對象器躏。年輕代中所有的存活對象(包括Edenq區(qū)和非空的那個 “from” 存活區(qū))都會被復(fù)制到 ”to“ 存活區(qū)俐载。GC過程完成后, ”to“ 區(qū)有對象,而 ‘from’ 區(qū)里沒有對象蟹略。兩者的角色進行正好切換 登失。

image

存活的對象會在兩個存活區(qū)之間復(fù)制多次,直到某些對象的存活時間達到一定的閥值挖炬。分代理論假設(shè), 存活超過一定時間的對象很可能會繼續(xù)存活更長時間揽浙。

image

這類“ 年老” 的對象因此被提升(promoted )到老年代。提升的時候意敛, 存活區(qū)的對象不再是復(fù)制到另一個存活區(qū),而是遷移到老年代, 并在老年代一直駐留, 直到變?yōu)椴豢蛇_對象馅巷。

此外GC會跟蹤記錄每個存活區(qū)對象存活的次數(shù),每次分代GC完成后草姻,存活對象的年齡就會+1钓猬。當(dāng)年齡超過提升閾值(tenuring threshold),就會被提升到老年代區(qū)域撩独。

MaxTenuringThreshold的判定

具體的提升閾值由JVM動態(tài)調(diào)整,但也可以用參數(shù) -XX:+MaxTenuringThreshold來指定上限敞曹。如果設(shè)置 -XX:+MaxTenuringThreshold=0 , 則GC時存活對象不在存活區(qū)之間復(fù)制账月,直接提升到老年代。現(xiàn)代 JVM 中這個閾值默認設(shè)置為15個GC周期澳迫。這也是HotSpot中的最大值局齿。

老年代(Old Generation)

老年代內(nèi)存空間一般情況下,里面的對象是垃圾的概率也更小橄登。

老年代GC發(fā)生的頻率比年輕代小很多抓歼。同時, 因為預(yù)期老年代中的對象大部分是存活的, 所以不再使用標(biāo)記和復(fù)制(Mark and Copy)算法。而是采用移動對象的方式來實現(xiàn)最小化內(nèi)存碎片拢锹。老年代空間的清理算法通常是建立在不同的基礎(chǔ)上的谣妻。原則上,會執(zhí)行以下這些步驟:

  1. 通過標(biāo)志位(marked bit),標(biāo)記所有通過 GC roots 可達的對象.
  2. 刪除所有不可達對象
  3. 整理老年代空間中的內(nèi)容,方法是將所有的存活對象復(fù)制,從老年代空間開始的地方,依次存放卒稳。

通過上面的描述可知, 老年代GC必須明確地進行整理,以避免內(nèi)存碎片過多拌禾。

永久代(PermGen)

Java8之前有一個特殊的空間,稱為“永久代”(Permanent Generation)展哭。

它存儲元數(shù)據(jù)(metadata)的地方,比如 class 信息等湃窍。此外,這個區(qū)域中也保存有其他的數(shù)據(jù)和信息, 包括內(nèi)部化的字符串(internalized strings)等等。

image

元數(shù)據(jù)區(qū)(Metaspace)

Java 8直接刪除了永久代(Permanent Generation)匪傍,改用Metaspace您市。將靜態(tài)變量和字符串常量都放到其中。像類定義(class definitions)之類的信息會被加載到Metaspace 中役衡。

image

元數(shù)據(jù)區(qū)位于本地內(nèi)存(native memory)茵休,不再影響到普通的Java對象。默認情況下, Metaspace的大小只受限于Java進程可用的本地內(nèi)存手蝎。

常見的垃圾回收思想的誤區(qū)

在我們的日常生活中垃圾收集主要就是找到垃圾并進行清理榕莺,這與我們JVM的運作機制恰恰相反,JVM中的垃圾收集器跟蹤和標(biāo)記所有正在使用的對象棵介,并把其余部分的對象當(dāng)做垃圾對象钉鸯。

所以這里一定要區(qū)分清楚,我們這里的標(biāo)記:是指標(biāo)記可用對象邮辽,而不是垃圾對象唠雕。常常會有人吧這兩者理解錯誤和混亂。

記住這一點以后吨述,我們再深入講解內(nèi)存自動回收的原理岩睁,探究JVM中垃圾收集的具體實現(xiàn)。先從基礎(chǔ)開始, 介紹垃圾收集的一般特征揣云、核心概念以及實現(xiàn)算法捕儒。

常見的垃圾回收類型

垃圾回收類型主要是通過回收的范圍進行界定和劃分。具體的JVM回收區(qū)域如下圖所示邓夕。

Java8之前

image

Java8之后

image

垃圾收集(Garbage Collection)通常分為:Minor GC - Major GC - Full GC 刘莹。接下來介紹這些事件及其區(qū)別亿笤,然后你會發(fā)現(xiàn)這些區(qū)別也不是特別清晰。

  • Minor GC:年輕代垃圾回收機制栋猖,屬于輕量級GC净薛,主要面向于年輕代區(qū)域的垃圾對象進行回收。
  • Major GC:老年代垃圾回收機制蒲拉,屬于重量級GC肃拜,主要面向于老年代區(qū)域的垃圾對象進行回收。
  • Full GC:完全化GC雌团,屬于全量極GC燃领,大致角度而言Major GCFull GC差不多,其實具體分析锦援,F(xiàn)ullGC的范圍是面向于整體的Heap堆內(nèi)存猛蔽。

GC的優(yōu)點和缺點(GC Benefits/Cost)

好處

  1. 提高系統(tǒng)的可靠性和穩(wěn)定性
  2. 內(nèi)存管理與程序設(shè)計的解耦
  3. 調(diào)試內(nèi)存錯誤所花費的時間更少
  4. 懸掛程序點/內(nèi)存泄漏不會發(fā)生

注意:Java程序沒有內(nèi)存泄漏;“不意味著對象存儲地址”更準(zhǔn)確)

壞處

  • GC暫停的時間長度
  • CPU/內(nèi)存利用率

Minor GC

年輕代內(nèi)存的垃圾收集稱為Minor GC灵寺。那什么時候會觸發(fā)MinorG以及出發(fā)MinorGC得我條件是什么曼库?

image
觸發(fā)MinorGC的時機

當(dāng)JVM無法為新對象分配Eden區(qū)的內(nèi)存空間時/達到了Eden存放閾值的時候會觸發(fā) Minor GC,所以新對象分配頻率越高略板,Minor GC的頻率就越高毁枯。并且Minor GC每次都會引起全線停頓(stop-the-world ),暫停所有的應(yīng)用線程叮称,對大多數(shù)程序而言,暫停時長基本上是可以忽略不計的种玛。

MinorGC回收的瓶頸

Eden區(qū)的對象基本上都是垃圾,也不怎么復(fù)制到Survior區(qū)/老年代瓤檐。如果情況不是這樣, 大部分新創(chuàng)建的對象不能被垃圾回收清理掉赂韵,則 Minor GC的停頓就會持續(xù)更長的時間。

MinorGC回收的范圍

Minor GC實際上忽略了老年代挠蛉,主要面向的對象范圍有兩部分組成:

  1. 主要是面向于老年代到年輕代的所引用的對象范圍祭示,例如,它會將從老年代指向年輕代的引用都被認為是GC Root碌秸,(而從年輕代指向老年代的引用在標(biāo)記階段全部被忽略)绍移。

  2. 主要面向的是Survior區(qū)之間的相互引用,此種場景的生命周期較短讥电,屬于年輕代之內(nèi)的對象之間的引用關(guān)系。

所以轧抗,Minor GC的定義很簡單恩敌、清理的就是年輕代,如下圖所示横媚。

image

Major GC vs Full GC

從上面我們知道了Minor GC清理的是年輕代空間(Young space)纠炮,相應(yīng)的其他區(qū)域也有對應(yīng)的回收機制和策略月趟。

  • Major GC清理的是老年代空間(Old space),MajorGC是由Minor GC觸發(fā)的恢口,所以很多情況下這兩者是不可分離的孝宗,G1這樣的垃圾收集算法執(zhí)行的是部分區(qū)域垃圾回收。

  • Full GC清理的是整個堆耕肩,包括年輕代和老年代空間因妇。

Minor GC、MajorGC和FullGC執(zhí)行效果

大部分情況下猿诸,發(fā)生在年輕代的Minor GC次數(shù)會很多婚被,會引起STW,也就是全局化暫停執(zhí)行業(yè)務(wù)線程的行為梳虽,但是時間很短(幾乎可以忽略不計)址芯。而Major GC和Full GC也會造成全局化暫停的效果。所以一般情況下盡可能減少MajorGC和FullGC是什么必要的窜觉,但是也不能“一棒子打死一船人”谷炸。必要的時候還是需要觸發(fā)少量幾次Major GC以及FullGC,進而釋放一些RSS常駐內(nèi)存禀挫。

垃圾收集(GC)的原理

自動內(nèi)存管理(Automated Memory Management)

如果要顯式地聲明什么時候需要進行內(nèi)存管理淑廊,實現(xiàn)自動進行收集垃圾,那樣就太方便了特咆,開發(fā)者不再耗費腦細胞去考慮要在何處進行內(nèi)存清理季惩。運行時環(huán)境會自動算出哪些內(nèi)存不再使用,并將其釋放腻格,歷史上第一款垃圾收集器是1959年為Lisp語言開發(fā)的画拾。

引用計數(shù)(Reference Counting)

共享指針方式的引用計數(shù)法, 可以應(yīng)用到所有對象菜职。許多語言都采用這種方法青抛,包括 Perl、Python 和 PHP 等酬核。下圖很好地展示了這種方式:

image

上圖中所展示的GC ROOTS蜜另,表示程序正在使用的對象。主要(這里指的不是全部)集中在于當(dāng)前正在執(zhí)行的方法中的局部變量或者是靜態(tài)變量等嫡意。在這里主要我指的是Java肴盏。

  • 藍色的圓圈表示可以引用到的對象源梭,里面的數(shù)字就是被引用計數(shù)器
  • 灰色的圓圈是各個作用域都不再引用的對象,可以被認為是垃圾母蛛,隨時會被垃圾收集器清理。
循環(huán)引用(detached cycle)的問題

引用計數(shù)器無法針對于循環(huán)引用這種場景進行正確的處理和探測。任何作用域中都沒有引用指向這些對象,但由于循環(huán)引用, 導(dǎo)致引用計數(shù)一直大于零忍些,如下圖所示。

image
  • 紅色線路和紅色圓圈對象實際上屬于垃圾引用以及垃圾對象坎怪,但由于引用計數(shù)的局限罢坝,所以存在內(nèi)存泄漏,永遠都無法進行回收該區(qū)域的對象內(nèi)存搅窿。
循環(huán)引用(detached cycle)的解決方案

比如說可以針對于一些這種循環(huán)模式進行加入到 “弱引用”(‘weak’ references)的體系中嘁酿,所以即使無法進行解決循環(huán)引用計數(shù)的場景,也可以通過弱引用實現(xiàn)內(nèi)存回收戈钢。

精華推薦 | 【JVM深層系列】「GC底層調(diào)優(yōu)系列」一文帶你徹底加強夯實底層原理之GC垃圾回收技術(shù)的分析指南(GC算法分析)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痹仙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子殉了,更是在濱河造成了極大的恐慌开仰,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薪铜,死亡現(xiàn)場離奇詭異众弓,居然都是意外死亡,警方通過查閱死者的電腦和手機隔箍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門谓娃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜒滩,你說我怎么就攤上這事滨达。” “怎么了俯艰?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵捡遍,是天一觀的道長。 經(jīng)常有香客問我竹握,道長画株,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任啦辐,我火速辦了婚禮谓传,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芹关。我一直安慰自己续挟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布充边。 她就那樣靜靜地躺著庸推,像睡著了一般常侦。 火紅的嫁衣襯著肌膚如雪浇冰。 梳的紋絲不亂的頭發(fā)上贬媒,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音肘习,去河邊找鬼际乘。 笑死,一個胖子當(dāng)著我的面吹牛漂佩,可吹牛的內(nèi)容都是我干的脖含。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼投蝉,長吁一口氣:“原來是場噩夢啊……” “哼养葵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘩缆,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤关拒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后庸娱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體着绊,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年熟尉,在試婚紗的時候發(fā)現(xiàn)自己被綠了归露。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡斤儿,死狀恐怖剧包,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情往果,我是刑警寧澤疆液,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站棚放,受9級特大地震影響枚粘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜飘蚯,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一馍迄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧局骤,春花似錦攀圈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽现喳。三九已至,卻和暖如春犬辰,著一層夾襖步出監(jiān)牢的瞬間嗦篱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工幌缝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灸促,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓涵卵,卻偏偏與公主長得像浴栽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轿偎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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