一文帶你瞭解 JVM 的垃圾回收機制

垃圾收集是一項自動化的技術。但是當咱們排查各類內存問題,或者當垃圾收集成爲系統達到更高併發量的瓶頸時,咱們須要對這些本來自動化的技術進行必要的監控和調節,全部咱們頗有必要學習 JVM 的垃圾收集機制。算法

一. 什麼區域須要回收?爲何須要回收?

垃圾回收也稱爲 GC (Garbage Collection),或者能夠稱爲垃圾收集。緩存

對於線程私有的三個部分(程序計數器,虛擬機棧和本地方法棧),不怎麼須要考慮回收問題,緣由:bash

  • 在方法結束或線程結束時,內存便跟着回收走了,他們隨線程而生,線程而滅
  • 並且對於棧來講,每一個棧幀中分配多少內存基本在類結構肯定下來的時候就已經肯定了。

對於線程共享的兩個部分(堆和方法區,主要是堆),須要考慮回收,緣由:併發

  • 程序只有處於運行的時候才能知道會建立哪些對象
  • 內存的分配和回收都是動態的

對於堆來講,若是不進行垃圾回收,內存早晚都會被消耗空,由於咱們在不斷的分配內存空間而不進行回收。除非內存無限大,咱們能夠任性的分配而不回收,可是事實並不是如此。因此,垃圾回收是必須的。高併發

二. 如何判斷對象是否存活?

在對堆進行垃圾回收前,必須肯定每一個對象是否還存活着;而這個判斷過程主要是如下兩種算法學習

1. 引用計數算法

給對象添加一個引用計數器,每當有一個地方引用它,計數器值加 1;每當引用失效,計數器減 1;當某個對象任什麼時候候計數器值都是 0 時,這個對象就「死」了ui

缺點:很難解決對象之間循環引用的問題,也所以主流的 JVM 都沒有使用該算法來管理內存spa

public class GCTest {
    Object object;

    private static void test() {
        GCTest test1 = new GCTest();
        GCTest test2 = new GCTest();
        
        test1.object = test2;
        test2.object = test1;  
        
        test1 = null;
        test2 = null;
    }
}
複製代碼

相似這樣的例子,因爲 test1 和 test2 相互引用對方,即便這兩個對象已經不可能再被訪問到(兩個變量都已經指向 null),引用計數算法也沒法讓垃圾收集器對它們進行回收線程

2. 可達性分析算法

這是主流 JVM 使用的回收算法code

經過一系列稱爲 GC Roots 的對象做爲起始點,從這些節點向下搜索,若是一個對象與 GC Roots 對象有引用鏈相連,說明對象可用;反之,對象不可用

如上圖的對象 4,5,6 則須要被回收

而可做爲 GC Roots 的對象包括下面幾種:

  • 虛擬機棧中引用的對象
  • 方法區中類靜態屬性引用的對象以及常量引用的對象
  • 本地方法棧中 native 方法引用的對象

三. 方法區的回收

方法區不多進行垃圾回收,甚至能夠不要求虛擬機對方法區進行回收,由於能回收的東西不多,所以也叫作永久代

在永久代,主要回收兩個內容:廢棄常量,無用的類。若是在永久代發生垃圾回收,那麼這兩個內容就會被清理出去(固然大多數狀況下不會去對永久代進行垃圾回收操做)

四. 引用的類型

不論使用什麼算法判斷對象的存活狀況,這都和「引用」息息相關

1. 強引用

  • 簡單來講就是相似 Object o = new Objrct 這樣的引用
  • 只要這樣的關係還存在,就永不會被回收

2. 軟引用

  • 還有用但非必需的對象
  • 若是將要發生內存溢出,則進行第二次回收,將這些軟引用對象回收;以後若是仍是沒有足夠的內存,再拋出內存溢出異常

能夠經過 SoftReference 實現,這是 sf 對 obj 有軟應用

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
sf.get(); //若是 obj 被標記爲須要被回收,則會返回null
複製代碼

SoftReference 能夠用來實現相似緩存的功能

3. 弱引用

  • 非必需對象,比軟引用強度更弱
  • 當垃圾收集器工做,它們就會被回收

能夠經過 WeakReference 實現,一般用於監控對象是否已經被垃圾回收器標記爲即將回收的垃圾

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
wf.get();
wf.isEnQueued();//返回是否被垃圾回收器標記爲即將回收的垃圾
複製代碼

4. 虛引用

  • 最弱的引用關係
  • 沒法經過虛引用取得一個對象的實例

能夠經過 PhantomReference 實現,主要用於檢測對象是否已經從內存中刪除

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
pf.get();//永遠返回null
pf.isEnQueued();//返回是否從內存中已經刪除
複製代碼

五. 垃圾回收算法

1. 標記——清除算法

先標記出要回收的對象,標記完成後統一清除這些對象。

缺點:

  • 效率過低,標記和清除兩個操做的效率都不高
  • 清除後會產生大量不連續的內存空間,或者稱爲內存碎片。而若是咱們須要分配一些較大的對象的時候,沒法找到足夠的連續空間是一件很麻煩的事情。

2. 複製算法

將內存劃分爲等大的兩塊,一次只使用一塊。當其中一塊用完了,就把裏面的存活的對象所有複製到另外一塊去,而後將已經使用的那一大塊一次性所有清理掉

  • 優勢:實現簡單,運行高效,也不用擔憂碎片問題
  • 缺點:將內存縮小了一半,代價有點高

3. 標記——整理算法

標記——清除算法的改進,在完成標記以後,讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存,故名叫「整理」。

4. 分代收集算法

當前商業虛擬機的垃圾收集都採用「分代收集」算法

分代收集算法將內存劃分爲新生代和老年代:

  • 在新生代中,每次垃圾收集都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集

  • 在老年代中由於對象存活率高、沒有額外空間對它進行擔保,就必須採用「標記 — 清理」或者「標記 — 整理」算法來回收。

相關文章
相關標籤/搜索