Java GC垃圾回收幾乎是面試必問的JVM問題之一,本篇文章帶領你們瞭解Java GC的底層原理,圖文並茂,突破學習及面試瓶頸。java
在上篇《JVM以內存結構詳解》中有些內容咱們沒有講,本篇結合垃圾回收機制來一塊兒學習。還記得JVM中堆的結構圖嗎?面試
圖中展現了堆中三個區域:Eden、From Survivor、To Survivor。從圖中能夠也能夠看到它們的大小比例,準確來講是:8:1:1。爲何要這樣設計呢,本篇文章後續會給出解答,仍是根據垃圾回收的具體狀況來設計的。算法
還記得在設置JVM時,經常使用的相似-Xms和-Xmx等參數嗎?對的它們就是用來講設置堆中各區域的大小的。segmentfault
(圖片來源於網絡)微信
控制參數詳解:網絡
對照上面兩個圖,再來看這些參數是否是沒有以前那麼枯燥了,它們在圖中都有了對應的位置。併發
有沒有發現沒有直接設置老年代空間大小的參數?咱們經過簡單的計算得到。jvm
老年代空間大小=堆空間大小-年輕代大空間大小
對上面參數當即了,但記憶有困難?那麼,如下幾個助記詞可能更好的幫你記憶和理解參數的含義。高併發
Xmx(memory maximum), Xms(memory startup), Xmn(memory nursery/new), Xss(stack size)。學習
對於參數的格式能夠這樣理解:
垃圾收集(Garbage Collection)一般被稱爲「GC」,由虛擬機「自動化」完成垃圾回收工做。
思考一個問題,既然GC會自動回收,開發人員爲何要學習GC和內存分配呢?爲了可以配置上面的參數配置?參數配置又是爲了什麼?
「當須要排查各類內存溢出,內存泄露問題時,當垃圾成爲系統達到更高併發量的瓶頸時,咱們就須要對GC的自動回收實施必要的監控和調節。」
JVM中程序計數器、虛擬機棧、本地方法棧3個區域隨線程而生隨線程而滅。棧幀隨着方法的進入和退出作入棧和出棧操做,實現了自動的內存清理。它們的內存分配和回收都具備肯定性。
所以,GC垃圾回收主要集中在堆和方法區,在程序運行期間,這部份內存的分配和使用都是動態的。
下面經過概念和具體的算法來了解GC垃圾回收的過程。
判斷對象常規有兩種方法:引用計數算法和可達性分析算法(Reachability Analysis)。
引用計數算法:給對象添加一個引用計數器,每當有一個地方引用它時計數器加1,引用釋放時計數減1,當計數器爲0時能夠回收。
引用計數算法實現簡單,判斷高效,在微軟COM和Python語言等被普遍使用,但在主流的Java虛擬機中沒有使用該方法,主要是由於沒法解決對象相互循環引用的問題。
可達性分析算法:基本思想是經過一系列稱爲「GC Root」的對象(如系統類加載器、棧中的對象、處於激活狀態的線程等)做爲起點,基於對象引用關係,開始向下搜索,所走過的路徑稱爲引用鏈,當一個對象到GC Root沒有任何引用鏈相連,證實對象是不可用的。
上圖中中綠色部分爲存活對象,灰色部分爲可回收對象。雖然灰色部份內部依舊有關聯,但它們到GC Root是不可達的。
面試官,說說Java GC都用了哪些算法?分別應用在什麼地方?
答:複製算法、標記清除、標記整理……
你還在單純的死記硬背麼?繼續往下看,你會豁然開朗,不再用死記硬背了。
標記清除(Mark-Sweep)算法,包含「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。
標記清除算法是最基礎的收集算法,後續的收集算法都是基於該思路並對其缺點進行改進而獲得的。
主要缺點:一個是效率問題,標記和清除過程的效率都不高;另外是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使,當程序在之後的運行過程當中須要分配較大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
複製(Copying)算法:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存用完了,就將還存活着的對象複製到另一塊上,而後清理掉前一塊。
每次對半區內存回收時、內存分配時就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
缺點:將內存縮小爲一半,性價比低,持續複製長生存期的對象則致使效率低下。
JVM堆中新生代便採用複製算法。回到最初推分配結構圖。
在GC回收過程當中,當Eden區滿時,還存活的對象會被複制到其中一個Survivor區;當回收時,會將Eden和使用的Survivor區還存活的對象,複製到另一個Survivor區,而後對Eden和用過的Survivor區進行清理。
若是另一個Survivor區沒有足夠的內存存儲時,則會進入老年代。
這裏針對哪些對象會進入老年代有這樣的機制:對象每經歷一次複製,年齡加1,達到晉升年齡閾值後,轉移到老年代。
在這整個過程當中,因爲Eden中的對象屬於像浮萍同樣「瞬生瞬滅」的對象,因此並不須要1:1的比例來分配內存,而是採用了8:1:1的比例來分配。
而針對那些像「水熊蟲」同樣,歷經屢次清理依舊存活的對象,則會進入老年代,而老年的清理算法則採用下面要講到的「標記整理算法」。
標記整理(Mark-Compact)算法:標記過程與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
這種算法不既不用浪費50%的內存,也解決了複製算法在對象存活率較高時的效率低下問題。
分代收集算法,基本思路:將Java的堆內存邏輯上分紅兩塊,新生代和老年代,針對不一樣存活週期、不一樣大小的對象採起不一樣的垃圾回收策略。
而在新生代中大多數對象都是瞬間對象,只有少許對象存活,複製較少對象便可完成清理,所以採用複製算法。而針對老年代中的對象,存活率較高,又沒有額外的擔保內存,所以採用標記整理算法。
其實,回頭看,分代收集算法就是對新生代和老年代算法從策略維度的規劃而已。
至此,當面試官再問Java GC都用到了哪些垃圾回收算法和分別應用在什麼場景下的問題,不再用死記硬背了吧?
關於Java GC還有垃圾收集器及垃圾回收調優,咱們將在後續文章中持續更新,歡迎關注公衆號「程序新視界」得到第一手更新。
原文連接:《面試官,不要再問我「Java GC垃圾回收機制」了》
系列文章:《JVM以內存結構詳解》
參考資料:《深刻理解java虛擬機》。
<center>程序新視界:精彩和成長都不容錯過</center>