很多小夥伴,都跟我反饋,說自己總是對JVM這一塊的學習和認識不夠紮實也不夠成熟,因為JVM的一些特性以及運作機制總是混淆以及不確定,導致面試和工作實戰中出現了很多的紕漏和短板,解決廣大小夥伴痛點,我寫了本篇文章,希望可以幫助大家夯實基礎和鍛造JVM技術功底。
在JVM領域中GC(Garbage Collection)翻譯為 「垃圾收集「,Garbage Collector翻譯為 「垃圾收集器」。
我們都知道在JVM中,執行垃圾收集需要停止整個應用(STW)。物件越多則收集所有垃圾消耗的時間就越長。程式中的大多數可回收的記憶體可歸為兩類:
這形成了分代資料模型。基於這一結構, VM中的記憶體被分為年輕代(Young Generation)和老年代(Old Generation),老年代有時候也稱為年老區(Tenured)。如下所示。
從上圖可以看出拆分為這樣兩個可清理的單獨區域,允許採用不同的演演算法來大幅提高GC的效能。
在不同分代中的物件可能會互相參照, 在收集某一個分代時就會成為 「事實上的」 GC root。當然,要著重強調的是,分代假設並不適用於所有程式。
GC演演算法專門針對「總體生命週期較短」,「總體生命週期較長」 這類特徵的物件來進行優化, JVM對收集那種存活時間半長不長的物件就顯得非常尷尬了,如下圖物件分佈。
堆記憶體中的記憶體池劃分也是類似的。不太容易理解的地方在於各個記憶體池中的垃圾收集是如何執行的。
Eden是記憶體中的一個區域, 用來分配新建立的物件。通常會有多個執行緒同時建立多個物件,所以Eden區被劃分為多個執行緒本地分配緩衝區(Thread Local Allocation Buffer, 簡稱TLAB)。通過這種緩衝區劃分,大部分物件直接由JVM 在對應執行緒的TLAB中分配, 避免與其他執行緒的同步操作。
如果 TLAB 中沒有足夠的記憶體空間, 就會在共用Eden區(shared Eden space)之中分配。如果共用Eden區也沒有足夠的空間, 就會觸發一次 年輕代GC 來釋放記憶體空間。如果GC之後 Eden 區依然沒有足夠的空閒記憶體區域, 則物件就會被分配到老年代空間(Old Generation)。
當Eden區進行垃圾收集時,GC將所有從root可達的物件過一遍, 並標記為存活物件。
物件間可能會有跨代的參照,所以需要一種方法來標記從其他分代中指向Eden的所有參照。這樣做又會遭遇各個分代之間一遍又一遍的參照。JVM在實現時採用了卡片標記(card-marking)。
JVM只需要記住Eden區中 「髒」物件的粗略位置,可能有老年代的物件參照指向這部分割區間。
Eden區的旁邊是兩個存活區, 稱為 from 空間和 to 空間。需要著重強調的的是, 任意時刻總有一個存活區是空的(empty)。
空的那個存活區用於在下一次年輕代GC時存放收集的物件。年輕代中所有的存活物件(包括Edenq區和非空的那個 「from」 存活區)都會被複制到 」to「 存活區。GC過程完成後, 」to「 區有物件,而 ‘from’ 區裡沒有物件。兩者的角色進行正好切換 。
存活的物件會在兩個存活區之間複製多次,直到某些物件的存活時間達到一定的閥值。分代理論假設, 存活超過一定時間的物件很可能會繼續存活更長時間。
這類「 年老」 的物件因此被提升(promoted )到老年代。提升的時候, 存活區的物件不再是複製到另一個存活區,而是遷移到老年代, 並在老年代一直駐留, 直到變為不可達物件。
此外GC會跟蹤記錄每個存活區物件存活的次數,每次分代GC完成後,存活物件的年齡就會+1。當年齡超過提升閾值(tenuring threshold),就會被提升到老年代區域。
具體的提升閾值由JVM動態調整,但也可以用引數 -XX:+MaxTenuringThreshold
來指定上限。如果設定 -XX:+MaxTenuringThreshold=0
, 則GC時存活物件不在存活區之間複製,直接提升到老年代。現代 JVM 中這個閾值預設設定為15個GC週期。這也是HotSpot中的最大值。
老年代記憶體空間一般情況下,裡面的物件是垃圾的概率也更小。
老年代GC發生的頻率比年輕代小很多。同時, 因為預期老年代中的物件大部分是存活的, 所以不再使用標記和複製(Mark and Copy)演演算法。而是採用移動物件的方式來實現最小化記憶體碎片。老年代空間的清理演演算法通常是建立在不同的基礎上的。原則上,會執行以下這些步驟:
通過上面的描述可知, 老年代GC必須明確地進行整理,以避免記憶體碎片過多。
Java8之前有一個特殊的空間,稱為「永久代」(Permanent Generation)。
它儲存後設資料(metadata)的地方,比如 class 資訊等。此外,這個區域中也儲存有其他的資料和資訊, 包括內部化的字串(internalized strings)等等。
Java 8直接刪除了永久代(Permanent Generation),改用Metaspace。將靜態變數和字串常數都放到其中。像類定義(class definitions)之類的資訊會被載入到Metaspace 中。
後設資料區位於本地記憶體(native memory),不再影響到普通的Java物件。預設情況下, Metaspace的大小隻受限於Java程序可用的本地記憶體。
在我們的日常生活中垃圾收集主要就是找到垃圾並進行清理,這與我們JVM的運作機制恰恰相反,JVM中的垃圾收集器跟蹤和標記所有正在使用的物件,並把其餘部分的物件當做垃圾物件。
所以這裡一定要區分清楚,我們這裡的標記:是指標記可用物件,而不是垃圾物件。常常會有人吧這兩者理解錯誤和混亂。
記住這一點以後,我們再深入講解記憶體自動回收的原理,探究JVM中垃圾收集的具體實現。先從基礎開始, 介紹垃圾收集的一般特徵、核心概念以及實現演演算法。
垃圾回收型別主要是通過回收的範圍進行界定和劃分。具體的JVM回收區域如下圖所示。
垃圾收集(Garbage Collection)通常分為:Minor GC - Major GC - Full GC 。接下來介紹這些事件及其區別,然後你會發現這些區別也不是特別清晰。
注意:Java程式沒有記憶體漏失;「不意味著物件儲存地址」更準確)
年輕代記憶體的垃圾收集稱為Minor GC。那什麼時候會觸發MinorG以及出發MinorGC得我條件是什麼?
當JVM無法為新物件分配Eden區的記憶體空間時/達到了Eden存放閾值的時候會觸發 Minor GC,所以新物件分配頻率越高,Minor GC的頻率就越高。並且Minor GC每次都會引起全線停頓(stop-the-world ),暫停所有的應用執行緒,對大多數程式而言,暫停時長基本上是可以忽略不計的。
Eden區的物件基本上都是垃圾,也不怎麼複製到Survior區/老年代。如果情況不是這樣, 大部分新建立的物件不能被垃圾回收清理掉,則 Minor GC的停頓就會持續更長的時間。
Minor GC實際上忽略了老年代,主要面向的物件範圍有兩部分組成:
主要是面向於老年代到年輕代的所參照的物件範圍,例如,它會將從老年代指向年輕代的參照都被認為是GC Root,(而從年輕代指向老年代的參照在標記階段全部被忽略)。
主要面向的是Survior區之間的相互參照,此種場景的生命週期較短,屬於年輕代之內的物件之間的參照關係。
所以,Minor GC的定義很簡單、清理的就是年輕代,如下圖所示。
從上面我們知道了Minor GC清理的是年輕代空間(Young space),相應的其他區域也有對應的回收機制和策略。
Major GC清理的是老年代空間(Old space),MajorGC是由Minor GC觸發的,所以很多情況下這兩者是不可分離的,G1這樣的垃圾收集演演算法執行的是部分割區域垃圾回收。
Full GC清理的是整個堆,包括年輕代和老年代空間。
大部分情況下,發生在年輕代的Minor GC次數會很多,會引起STW,也就是全域性化暫停執行業務執行緒的行為,但是時間很短(幾乎可以忽略不計)。而Major GC和Full GC也會造成全域性化暫停的效果。所以一般情況下儘可能減少MajorGC和FullGC是什麼必要的,但是也不能「一棒子打死一船人」。必要的時候還是需要觸發少量幾次Major GC以及FullGC,進而釋放一些RSS常駐記憶體。
如果要顯式地宣告什麼時候需要進行記憶體管理,實現自動進行收集垃圾,那樣就太方便了,開發者不再耗費腦細胞去考慮要在何處進行記憶體清理。執行時環境會自動算出哪些記憶體不再使用,並將其釋放,歷史上第一款垃圾收集器是1959年為Lisp語言開發的。
共用指標方式的參照計數法, 可以應用到所有物件。許多語言都採用這種方法,包括 Perl、Python 和 PHP 等。下圖很好地展示了這種方式:
上圖中所展示的GC ROOTS,表示程式正在使用的物件。主要(這裡指的不是全部)集中在於當前正在執行的方法中的區域性變數或者是靜態變數等。在這裡主要我指的是Java。
參照計數器無法針對於迴圈參照這種場景進行正確的處理和探測。任何作用域中都沒有參照指向這些物件,但由於迴圈參照, 導致參照計數一直大於零,如下圖所示。
比如說可以針對於一些這種迴圈模式進行加入到 「弱參照」(‘weak’ references)的體系中,所以即使無法進行解決迴圈參照計數的場景,也可以通過弱參照實現記憶體回收。
精華推薦 | 【JVM深層系列】「GC底層調優系列」一文帶你徹底加強夯實底層原理之GC垃圾回收技術的分析指南(GC演演算法分析)
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/17065291.html,任何足夠先進的科技,都與魔法無異。