jvm 的垃圾回收機制

說到垃圾回收,肯定要知道jvm的分區,jvm 主要分爲堆,虛擬機棧,程序計數器,方法區(元數據區),本地方法棧。其中虛擬機棧,程序計數器,和本地方法棧是線程級別的,生死跟隨線程,所以是能確定銷燬時間的。所以垃圾回收會集中在堆和方法區上。至於原因,堆是線程共享的區域,而內存的分配和回收是動態,所以需要特別關注這兩個區域。

在進行垃圾回收前,肯定要確定垃圾回收的應該是那些對象,確定那些應該回收,那些不應該回收。這一部分也就是面試官最喜歡問的垃圾回收算法。

引用計數法

引用計數法是最早期的策略,既對象實例被創建出來就有一個引用計數,而該對象實例沒分配一個變量該計數值加1,當對象實例的引用超過了聲明週期或者被分配了新值,引用計數-1。而垃圾回收的就是計數值爲0的對象。

這樣做的好處就是引用計數器可以在程序中運行中計算,垃圾回收執行的很快,適合程序不能被長期打斷的環境中運行,缺點就是不能檢測循環引用。循環引用的程序計數器永遠不會爲0。

可達性分析算法

可達性分析算法是從離散數學中引入的,程序把所有的引用看做是一張圖,從一個節點開始,尋找這個節點引用的對應節點,以此類推查找節點,當所有的幾點都查找完畢,那些沒有被查到的節點就被認爲是沒有被引用的可回收對象。

這樣就可以避免循環引用不能被回收的問題,如何保證被引用的對象不被回收呢?這就要從可達性分析的開始節點來說了

一般可達性分析的幾點爲一下幾個:

虛擬機棧中棧幀中的程序變量表中對對象的引用;

方法區中類靜態屬性引用的對象;

方法區中常量引用的對象;

本地方法棧中引用的對象;

可達性分析對一次查找後的不可達對象進行標記,第一次標記後緊接着後進行二次標記,會查找當對象是否重寫了fanlize()方法並在方法內重新建立連接關係,如果建立關係,將會逃離本次回收。

方法區的回收:

方法區的回收主要是廢棄的常量和無用的類。對於常量來說可以用可達性分析來計算,但是類的回收要滿足一下幾個條件。

1、該類的實例全部已經被回收。

2、加載該類的加載器已經被回收。

3、對應該類的java.lang.Class對象沒有任何地方引用。

同時滿足以上三個條件方法區的類纔會被GC回收。

常用的垃圾回收算法

1、引用計數法:實現簡單,效率較高。一般情況下是很好的選擇。

2、標記-清除算法:基礎的垃圾回收算法,容易實現,原理簡單:分爲兩個階段:標記階段和清除階段,標記階段標記那些需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。

其缺點就是容易產生內存碎片,碎片過多就會導致後期大對象申請空間失敗,導致提前觸發新一次的垃圾回收。

3、複製算法:爲了改進標記清除算法,複製算法被提出,原理就是把內存空間分成兩部分,開始只在一部分中生成對象,當內存滿了之後就觸發算法,將存活的類copy 到另一片區域,然後將這一片空間整體回收,解決了標記清除中內存碎片的問題,

但是這樣就造成可用內存縮小,垃圾回收機制更頻繁的觸發,如果存貨的類越多,算法的效率就越低。

4、標記-整理算法:爲了解決內存縮小問題,提出了標記-整理算法,標記和標記-清除的算法一樣,當時清除時是先將存活的類向一端移動,然後將端終點以外的內存清理掉。

5、分代收集算法:目前大部分jvm採用的垃圾回收算法,思路就是將堆內存分爲幾個不同分區,一般分爲老年代和新生代。他們的特點就是來年代垃圾回收時只有少量的對象需要回收,而新生代擇優大量的對象需要回收。那麼根據不同的分區就可以採用不同的回收算法。

新生代由於每次都會有大量的對象回收,存活對象較少,所以使用複製算法比較合適,新生代劃分也不是1:1 而是分爲三片區域 Eden區和兩個Survivor區比例爲8:1:1,新對象創建主要在Eden區。當Eden區滿了的話就會將存活的對象copy 到survivor0區,然後清除Eden區,當這兩個區都滿了,就會將這兩個區存活的對象複製到survivor1區,然後清除這兩個區,然後將survivor0和survivor1兩個區交換,保持survivor1區爲空。

當新生代分區全滿了之後,存活的對象會被放入老年代,如果老年代也滿了,就會觸發full GC (新生代和老年代一起回收)

老年代對象存活的對象一般是經過新生代的數次回收依然存活的生命週期比較長的對象,當老年代滿了就會觸發full GC,因爲老年代的對象生命週期比較長,標記次數多,另外老年代的內存空間比新生代高一倍左右,所以full GC的機率比較低。

 

 

 

發佈了10 篇原創文章 · 獲贊 2 · 訪問量 1415
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章