JVM垃圾回收機制


一、回收堆區

垃圾回收器在堆進行垃圾回收前,首先要判斷這些對象那些還存活,那些已經“死去”。判斷對象是否已“死”有如下幾種算法:

1.引用計數法

給對象增加一個引用計數器,每當有一個地方引用它時,計數器就+1;

當引用失效時,計數器就-1;

任何時刻計數器為0的對象就是不能再被使用的,即對象已“死”。

引用計數法實現簡單,判定效率也比較高,在大部分情況下都是一個比較好的算法。

但是,在主流的JVM中沒有選用引用計數法來管理內存,最主要的原因是引用計數法無法解決對象的循環引用問題。

2. 可達性分析算法

在上面講了,Java並不采用引用計數法來判斷對象是否已“死”,而采用“可達性分析”來判斷對象是否存活。

通過一系列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索走過的路徑稱為“引用鏈”,當一個對象到 GC Roots 沒有任何的引用鏈相連時(從 GC Roots 到這個對象不可達)時,證明此對象不可用。以下圖為例:

在Java語言中,可作為GC Roots的對象包含以下幾種:

  1. 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  2. 方法區中靜態屬性引用的對象
  3. 方法區中常量引用的對象
  4. 本地方法棧中(Native方法)引用的對象

引用

JDK1.2以前,Java中引用的定義很傳統: 如果引用類型的數據中存儲的數值代表的是另一塊內存的起始地址,就稱這塊內存代表着一個引用。

這種定義有些狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態。

JDK1.2之后,Java對引用的概念做了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)四種,這四種引用的強度依次遞減。

1.強引用

類似於"Object obj = new Object()"這類的引用,只要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象實例。

當內存空 間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。

2.軟引用

如果一個對象只具有軟引用,那就類似於可有可無的生活用品。

如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。

軟引用可用來實現內存敏感的高速緩存。

舉例:查看網頁,可能后退,那么剛才的網頁要不要一直存儲,一直存儲就是強引用,不存儲就是回收,那么折中一下,於是產生了弱引用,當內存空間足夠時,就不回收,不足時,再回收。這個根據內存敏感程度而變化而決定是否緩存,就是內存敏感的高速緩存。

3.弱引用

對象擁有更短暫的生命周期。

在gc線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間是否充足,都會回收它的內存。

由於gc是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。

4.虛引用

就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。

如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。

作用: 虛引用主要用來跟蹤對象被垃圾回收的活動

區別: 虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。

當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。

如果程序發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。

3.真正判決

要宣告一個對象的真正死亡,至少要經歷兩次標記過程

如果對象在進行可達性分析之后發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選。

篩選的條件是此對象是否有必要執行finalize()方法。

​ 沒必要:沒有覆蓋finalize()方法finalize()方法已經被調用過一次了

審判

如果這個對象被判定為有必要執行finalize()方法,那么這個對象將會被放置在一個叫做F-Queue的隊列之中,並在稍后由一個虛擬機自動建立的、低優先級的Finalizer線程去執行它

如果對象在finalize()中成功拯救自己:與引用鏈上的任何一個對象建立起關聯關系

那在第二次標記時它將會被移除出"即將回收"的集合,也就暫時逃脫死亡的命運了。

如果對象這時候還是沒有逃脫,那基本上它就是真的被回收了。

二、回收方法區

方法區(永久代)的垃圾回收主要收集兩部分內容:廢棄常量無用類。

廢棄常量:

沒有任何一個String對象引用常量池中的"abc"常量,也沒有其他地方引用這個字面量,如果此時發生GC並且有必要的話,這個"abc"常量會被系統清理出常量池。

常量池中的其他類(接口)、方法、字段的符號引用也與此類似。

無用類

1.該類的所有實例都已經被回收(即在Java堆中不存在任何該類的實例)
2.加載該類的ClassLoader已被回收
3.該類對應的Class對象沒有任何其他地方被引用,無法在任何地方通過反射訪問該類的方法

三、垃圾回收算法

1.標記-清除算法

首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象

“標記-清除”算法的不足主要有兩個

  1. 效率問題:標記和清除這兩個過程的效率都不高
  2. 空間問題:標記清除后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行中需要分配較大對象時,無法找到足夠連續內存而不得不提前觸發另一次垃圾收集。

2.復制算法(新生代回收算法)

它將可用內存按容量划分為大小相等的兩塊,每次只使用其中一塊。

當這塊內存需要進行垃圾回收時,會將此區域還存活着的對象復制到另一塊上面,然后再把已經使用過的內存區域一次清理掉。

因為復制過去后,另一邊的內存肯定是連續的了,此時再把使用過得內存區域清理,從而達到了整理的效果。

也就是伊甸園區移動到survive0和survive1區的算法。

但是,伊甸園區的對象都是朝生夕死的,所以並不需要1:1的空間,所以出現了8:1:1的默認比例

3.標記整理算法(老年代回收算法)

復制收集算法在對象存活率較高時會進行比較多的復制操作,效率會變低。因此在老年代一般不能使用復制算法。

而是采用標記整理算法

標記過程仍與“標記-清除”過程一致,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活對象向一端移動,然后直接清理掉除存活對象以外的內存。流程圖如下:

4.分代收集算法

就是將堆區分開,不同的位置采用不同的算法

新生代中,每次垃圾回收都有大批對象死去,只有少量存活,因此我們采用復制算法;

老年代中對象存活率高,就必須采用"標記-清理"或者"標記-整理"算法。

四 .Minor GC、Major GC、Full GC的區別?

Minor GC 又稱為新生代GC 指的是發生在新生代的垃圾回收操作(包括Eden區和Survivor區)。

當年輕代內存空間被用完時,就會觸發垃圾回收。這個垃圾回收叫做Minor GC。

Major GC通常是跟full GC是等價的,收集整個GC堆。

但因為HotSpot VM發展了這么多年,外界對各種名詞的解讀已經完全混亂了

Full GC定義是相對明確的,就是針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC。

針對HotSpot VM GC來看

它里面的GC其實准確分類只有兩大種:

Partial GC:並不收集整個GC堆的模式

  • Young GC:只收集年輕代的GC
  • Old GC:只收集老年代的GC。只有CMS的concurrent collection是這個模式
  • Mixed GC:收集整個年輕代以及老年代的GC。只有G1有這個模式

Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM