JVM 垃圾回收算法和垃圾回收器

垃圾回收的區域

  • 棧:棧中的生命週期是跟隨線程,因此通常不須要關注
  • 堆:堆中的對象是垃圾回收的重點
  • 方法區:這一塊也會發生垃圾回收,不過這塊的效率比較低,通常不是咱們關注的重點

怎麼判斷對象的存活

通常有兩種方式(引用計數法、可達性分析),JVM使用的是可達性分析算法

引用計數法

給對象添加一個引用計數器,當對象增長一個引用時計數器加 1,引用失效時計數器減 1。引用計數爲 0 的對象可被回收(Python 在用,但主流虛擬機沒有使用)緩存

  • 優勢:快,方便,實現簡單
  • 缺陷:對象相互引用時(A.instance=B 同時 B.instance=A),很難判斷對象是否該回收

可達性分析

來斷定對象是否存活的。這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是不可用的bash

做爲 GC Roots 的對象包括下面幾種:服務器

  1. 當前虛擬機棧中局部變量表中的引用的對象
  2. 方法區中類靜態屬性引用的對象
  3. 方法區中的常量引用的對象

finalize

Java提供finalize()方法,垃圾回收器準備釋放內存的時候,會先調用finalize(),能夠完成對象的拯救(不被回收),可是不能保證必定不被回收,說白了就是沒啥用,一個坑多線程

各類引用(Reference)

Reference 中存儲的數據表明的是另外一塊內存的起始地址併發

強引用

通常的 Object obj = new Object() ,就屬於強引用。 (若是有 GCroots 的強引用)垃圾回收器絕對不會回收它,當內存不足時寧願拋出 OOM 錯誤,使得程序異常中止,也不會回收強引用對象jvm

軟引用

SoftReference垃圾回收器在內存充足的時候不會回收它,而在內存不足時會回收它網站

示例代碼:spa

public static void main(String[] args) {
    String str = new String("SunnyBear"); // 強引用
    SoftReference<String> strSoft = new SoftReference<String>(str);
    str = null; // 幹掉強引用,確保只有strSoft的軟引用
    System.out.println(strSoft.get()); // SunnyBear
    System.gc(); // 執行一次gc,此命令請勿在線上使用,僅做示例操做
    System.out.println("------------ gc after");
    System.out.println(str); // null
    System.out.println(strSoft.get()); // SunnyBear
}
複製代碼

因此軟引用通常用來實現一些內存敏感的緩存,只要內存空間足夠,對象就會保持不被回收掉線程

弱引用 WeakReference

垃圾回收器在掃描到該對象時,不管內存充足與否,都會回收該對象的內存

示例代碼:

public static void main(String[] args) {
    String str = new String("SunnyBear"); // 強引用
    WeakReference<String> strWeak = new WeakReference<String>(str);
    str = null; // 幹掉強引用,確保只有strSoft的軟引用
    System.out.println(strWeak.get()); // SunnyBear
    System.gc(); // 執行一次gc,此命令請勿在線上使用,僅做示例操做
    System.out.println("------------ gc after"); // null
    System.out.println(str); // null
    System.out.println(strWeak.get()); // null
}
複製代碼

實際應用,如WeakHashMap、ThreadLocal

虛引用 PhantomReference

幽靈引用,最弱,被垃圾回收的時候收到一個通知,若是一個對象只具備虛引用,那麼它和沒有任何引用同樣,任什麼時候候均可能被回收

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

GC

Minor GC

  • 特色: 發生在新生代上,發生的較頻繁,執行速度較快
  • 觸發條件: Eden 區空間不足/空間分配擔保

Full GC

  • 特色:主要發生在老年代上(新生代也會回收),較少發生,執行速度較慢
  • 觸發條件:
    • 調用 System.gc()
    • 老年代區域空間不足
    • 空間分配擔保失敗
    • JDK 1.7 及之前的永久代(方法區)空間不足

垃圾回收算法

複製算法(Copying)

將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要按順序分配內存便可, 實現簡單,運行高效。只是這種算法的代價是將內存縮小爲了原來的一半。

複製算法

  • 優勢
    • 簡單高效,不會出現內存碎片
  • 缺點
    • 內存利用率低
    • 存活對象較多時效率明顯下降,由於須要移動每一個不可回收數據的內存實際位置

注: 專門研究代表,新生代中的對象 90%是「朝生夕死」的,因此通常來講回收佔據 10% 的空間夠用了,因此並不須要按照 1:1 的比例來劃份內存空間,而是 將內存分爲一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor[1]。當回收時,將 Eden 和 Survivor 中還存活着的對象一 次性地複製到另一塊 Survivor 空間上,最後清理掉 Eden 和剛纔用過的 Survivor 空間。 HotSpot 虛擬機默認 Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代中可用內存空間爲整個新生代容量的 90%(80%+10%),只有 10%的內存會被 「浪費」。

標記-清除算法(Mark-Sweep)

首先標記全部須要回收的對象,而後統一回收被標記的對象

標記清除算法

  • 優勢
    • 利用率100%
  • 缺點
    • 標記和清除效率都不高(對比複製算法)
    • 會產生大量不連續的內存碎片

標記-整理算法(Mark-compact)

首先標記出全部須要回收的對象,在標記完成後,後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端,邊界之外的內存

標記整理算法

  • 優勢
    • 利用率100%
    • 沒有內存碎片
  • 缺點
    • 標記和清除效率都不高(對比複製算法及標記清楚算法)

垃圾回收器

jvm 垃圾回收器把上面的三種算法所有用到了,採用分代收集

  • 新生代:複製算法
收集器 收集對象和算法 收集器類型
Serial 新生代,複製算法 單線程
ParNew 新生代,複製算法 並行的多線程收集器
Parallel Scavenge 新生代,複製算法 並行的多線程收集器
  • 老年代:標記清除算法和標記整理算法
收集器 收集對象和算法 收集器類型
Serial Old 老年代,標記整理算法 單線程
Parallel Old 老年代,標記整理算法 並行的多線程收集器
CMS(Conc Mark Sweep ) 老年代,標記清除算法 並行和併發收集器
G1(Garbage First) 跨新生代和老年代,複製算法 + 標記整理算法 並行和併發收集器

注:

  • 並行:垃圾收集的多線程的同時進行
  • 併發:垃圾收集的多線程和用戶應用的多線程同時進行
  • 使用 jps -v 能夠看到使用的垃圾收集器,例如:-XX:+UseConcMarkSweepGC (CMS)

能夠配套使用的垃圾回收器

連線表示能夠 新生代老年代 配套使用的垃圾收集器

Serial/Serial Old

最古老的,單線程,獨佔式,成熟,適合單 CPU 服務器 -XX:+UseSerialGC 新生代和老年代都用串行收集器

ParNew

ParNew 和 Serial 基本沒區別,惟一的區別:多線程,多 CPU 的,停頓時間比 Serial 少

-XX:+UseParNewGC 新生代使用 ParNew,老年代使用 Serial Old

能夠和CMS搭配使用

Parallel Scavenge(ParallerGC)/Parallel Old

關注吞吐量的垃圾收集器,高吞吐量則能夠高效率地利用 CPU 時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。所謂吞吐量就是 CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了 100 分鐘,其中垃圾收集花掉 1 分鐘,那有吞吐效率就是 99%

CMS(Concurrent Mark Sweep)

收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的 Java 應用集中在互聯網站或者 B/S 系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。

CMS 收集器就很是符合這類應用的需求。-XX:+UseConcMarkSweepGC通常新生代使用 ParNew,老年代的用 CMS,從名字(包含「Mark Sweep」)上就能夠看出,CMS 收集器是基於「標記—清除」算法實現的,它的運做過程相對於前面幾種收集器來講更復雜一些

回收過程

整個過程分爲 4 個步驟,包括:

  1. 初始標記:僅僅只是標記一下 GC Roots 能直接關聯到的對象,速度很快,須要停頓(STW -Stop the world)
  2. 併發標記:從 GC Root 開始對堆中對象進行可達性分析,找到存活對象,它在整個回收過程當中耗時最長,不須要停頓
  3. 從新標記:爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,須要停頓(STW)。這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短
  4. 併發清除:不須要停頓

優缺點

  • 優勢
    • 因爲整個過程當中耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做,因此,從整體上來講,CMS 收集器的內存回收過程是與用戶線程一塊兒併發執行的
  • 缺點
    • CPU 資源敏感:由於併發階段多線程佔據 CPU 資源,若是 CPU 資源不足,效率會明顯下降
    • 因爲 CMS 併發清理階段 用戶線程還在運行着,伴隨程序運行天然就還會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS 沒法在當次收集中處理掉它們,只好留待下一次 GC 時再清理掉。這一部分垃圾就稱爲 浮動垃圾
    • 因爲浮動垃圾的存在,所以須要預留出一部份內存,意味着 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。在 1.6 的版本中老年代空間使用率閾值(92%),若是預留的內存不夠存放浮動垃圾,就會出現 Concurrent Mode Failure,這時虛擬機將臨時啓用 Serial Old 來替代 CMS。
    • 會產生內存碎片:標記-清除算法 會致使產生不連續的內存碎片

G1

G1相比較CMS的改進

  • 基於標記-整理算法, 不會產生空間碎片,分配大對象時不會沒法獲得連續的空間而提早觸發一次full gc
  • 停頓時間可控: G1能夠經過設置預期停頓時間(Pause time)來控制垃圾收集時間,可是這個預期停頓時間G1只能儘可能作到,而不是必定能作到

可預測的停頓:

G1 收集器之因此能創建可預測的停頓時間模型,是由於它能夠有計劃地避免在整個 Java 堆中進行全區域的垃圾收集。G1 跟蹤各個 Region 裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的 Region(這也就是 Garbage-First 名稱的來由)。這種使用 Region 劃份內存空間以及有優先級的區域回收方式,保證了 G1 收集器在有限的時間內能夠獲取儘可高的收集效率

G1的設置參數

  • -XX:+UseG1GC // 開啓G1
  • -XX:MaxGCPauseMillis=200 // 預期停頓時間200毫秒,默認也是200
  • -XX:G1HeapRegionSize=2 // 設置每一個區域大小2M,其必須是2的冪,範圍容許爲1Mb到32Mb
  • -XX:G1NewSizePercent // 新生代最小值,默認值 5%
  • -XX:G1MaxNewSizePercent // 新生代最大值,默認值 60%
  • -XX:ParallelGCThreads // STW 期間,並行 GC 線程數
  • -XX:ConcGCThreads=n // 併發標記階段,並行執行的線程數

G1是怎麼劃堆內存的呢

G1 把堆劃分紅多個大小相等的 獨立區域(Region),新生代和老年代再也不物理隔離

G1 算法將堆劃分爲若干個獨立區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停全部應用線程的方式,將存活對象拷貝到老年代或者 Survivor 空間。例如其中一個獨立區域如圖:

獨立區域

GC 的模式

1. Young GC

Young GC 主要是對 Eden 區進行 GC,它在 Eden 空間耗盡時會被觸發。在這種狀況下,Eden 空間的數據移動到 Survivor 空間中,若是 Survivor 空間不夠,Eden 空間的部分數據會直接晉升到老年代空間。Survivor 區的數據移動到新的 Survivor 區中,也有部分數據晉升到老年代空間中。最終 Eden 空間的數據爲空,GC 中止工做,應用線程繼續執行

2. Mixed GC

選定全部新生代裏的 Region,外加根據 global concurrent marking 統計得出收集收益高的若干老年代 Region。在用戶指定的開銷目標範圍內儘量選擇益高的老年代 Region。Mixed GC 不是 full GC,它只能回收部分老年代的 Region。若是 mixed GC 實在沒法跟上程序分配內存的速度,致使老年代填滿沒法繼續進行 Mixed GC,就會使用 serial old GC(full GC)來收集整個 GC heap。因此咱們能夠知道,G1 是不提供 full GC 的

收集過程

大體分爲4個步驟:

  1. 初始標記:僅僅只是標記一下 GC Roots 能直接關聯到的對象,而且修改 TAMS(Nest Top Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確能夠的 Region 中建立對象,此階段須要停頓線程(STW),但耗時很短
  2. 併發標記:從 GC Root 開始對堆中對象進行可達性分析,找到存活對象,此階段耗時較長,但可與用戶程序併發執行
  3. 最終標記:爲了修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的 Remembered Set Logs 裏面,最終標記階段須要把 Remembered Set Logs 的數據合併到 Remembered Set 中。這階段須要停頓線程(STW),可是可並行執 行
  4. 篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據用戶所指望的 GC 停頓時間來制定回收計劃。此階段其實也能夠作到與用戶程序一塊兒併發執行,可是由於只回收一部分 Region,時間是用戶可控制的,並且停頓用戶線程將大幅度提升收集效率

垃圾回收器的一些重要參數

參數 描述
UseSerialGC 虛擬機運行在 Client 模式下的默認值,打開此開關後,使用 Serial+Serial Old 的收集器組合進行內存回收
UseParNewGC 打開此開關後,使用 ParNew + Serial Old 的收集器組合進行內存回收
UseConcMarkSweepGC 打開此開關後,使用 ParNew + CMS + Serial Old 的收集器組合進行內存回收。Serial Old 收集器將做爲 CMS 收集器出現 Concurrent Mode Failure 失敗後的後備收集器使用
UseParallelGC 虛擬機運行在 Server 模式下的默認值,打開此開關後,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器組合進行內存回收
UseParallelOldGC 打開此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收
SurvivorRatio 新生代中 Eden 區域與 Survivor 區域的容量比值,默認爲 8,表明 Eden : Survivor = 8 : 1
PretenureSizeThreshold 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配
MaxTenuringThreshold 晉升到老年代的對象年齡,每一個對象在堅持過一次 Minor GC 以後,年齡就增長 1,當超過這個參數值時就進入老年代
UseAdaptiveSizePolicy 動態調整 Java 堆中各個區域的大小以及進入老年代的年齡
HandlePromotionFailure 是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個 Eden 和 Survivor 區的全部對象都存活的極端狀況
ParallelGCThreads 設置並行 GC 時進行內存回收的線程數
GCTimeRatio GC 時間佔總時間的比率,默認值爲 99,即容許 1% 的 GC 時間,僅在使用 Parallel Scavenge 收集器生效
MaxGCPauseMillis 設置 GC 的最大停頓時間,僅在使用 Parallel Scavenge 收集器時生效
CMSInitiatingOccupancyFraction 設置 CMS 收集器在老年代空間被使用多少後觸發垃圾收集,默認值爲 68%,僅在使用 CMS 收集器時生效
UseCMSCompactAtFullCollection 設置 CMS 收集器在完成垃圾收集後是否要進行一次內存碎片整理,僅在使用 CMS 收集器時生效
CMSFullGCsBeforeCompaction 設置 CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理,僅在使用 CMS 收集器時生效
相關文章
相關標籤/搜索