天天看點

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

前言

Github:https://github.com/yihonglei/jdk-source-code-reading(java-jvm)

JVM記憶體結構

JVM類加載機制

JVM記憶體溢出分析

HotSpot對象建立、記憶體、通路

JVM垃圾回收機制(1)--如何判定對象可以回收

JVM垃圾回收機制(2)--垃圾收集算法

JVM垃圾回收機制(3)--垃圾收集器

JVM垃圾回收機制(4)--記憶體配置設定和回收政策

一 垃圾收集器概述

垃圾收集器是垃圾收集算法(标記-清除算法、複制算法、标記-整理算法)的具體實作。

Java 虛拟機規範中對垃圾收集器應該如何實作并沒有任何規定,是以不同的廠商、

不同版本的虛拟機所提供的垃圾收集器都可能會有很大的差别,并且一般都會提供參

數供使用者根據自己的應用特點和要求組合出各個年代所使用的收集器。

這裡主要讨論 HotSpot 虛拟機中的垃圾收集器。

1、垃圾收集器組合

JDK7/8後HotSpot虛拟機中的垃圾收集器群組合搭配示意圖:    

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

圖中表明總的有七種收集器,Serial、ParNew、Parallel Scavenge、Serial Old、

Parallel Old、CMS、G1;

新生代收集器:Serial、ParNew、Parallel Scavenge。

老年代收集器:Serial Old、Parallel Old、CMS。

整理收集器:G1。

這些收集器很多是可以組合使用的,圖中連線的表示可以搭配使用。

2、并發和并行垃圾收集的差別

并行(Parallel)

指多條垃圾收集線程并行工作,但此時使用者線程仍然處于等待狀态;

如 ParNew、Parallel Scavenge、Parallel Old;

并發(Concurrent)

指使用者線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行);

使用者程式在繼續運作,而垃圾收集程式線程運作于另一個 CPU 上;    

如 CMS、G1(也有并行);

3、Minor GC和Full GC的差別

Minor GC 又稱為新生代 GC,是指發生在新生代的垃圾收集動作。

因為大多數 Java 對象都是"朝生夕滅"的,是以 Minor GC 回收非常的頻繁,一般回收速度

也非常快。

Full GC又稱為 Major GC 或老年代 GC,是指發生在老年代的垃圾收集動作。

出現 Full GC 經常會伴随至少一次的 Minor GC(不是絕對,Parallel Sacvenge 收集器就

可以選擇設定 Major GC 政策);

Major GC 速度一般比 Minor GC 慢 10 倍以上;

接下來主要分析各個收集器的特性、基本原理和使用場景。

二 Serial收集器

Serial (串行)收集器是最基本、發展曆史最悠久的收集器,在 JDK1.3.1 之前是虛拟機新生代

收集的唯一選擇。

1、特性

Serial 收集器主要有以下特性:

1)針對新生代進行回收。

2)采用複制算法。

3)單線程收集。

4)回收時必須停止其他所有工作線程,直到收集結束。即"Stop The Word",給使用者的體驗不好。

2、基本原理

Serial/Serial Old 收集器的運作過程示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

采用單線程進行收集,收集時停止所有使用者工作線程,直到收集完成,收集完成才又啟動

使用者線程。

3、使用場景

Serial 收集器依然是 HotSpot 在 Client 模式下預設的新生代收集器;

也有優于其他收集器的地方:簡單高效(與其他收集器的單線程相比);對于限定單個 CPU

的環境來說,Serial 收集器沒有線程互動(切換)開銷,可以獲得最高的單線程收集效率;

在使用者的桌面應用場景中,可用記憶體一般不大(幾十 M 至一兩百 M),可以在較短時間

内完成垃圾收集(幾十 MS 至一百多 MS),隻要不頻繁發生,這是可以接受的。

4、設定參數

"-XX:+UseSerialGC":添加該參數來顯式的使用串行垃圾收集器;

三 ParNew收集器

ParNew 收集器是 Serial 收集器的多線程版本。

1、特性

1)多線程收集;

2)除多線程外,其餘特性與 Serial(串行)收集器一樣,比如控制參數、收集算法

(都用複制算法)、Stop The World(收集時停止所有使用者線程)、對象配置設定規則、

回收政策等都與 Serial 收集器完全一樣。ParNew 和 Serial 實作代碼很多都是共用的,

可以看成是對 Serial 收集效率提升的優化版本。

2、基本原理

ParNew 收集器工作過程示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

采用多線程收集,收集時也沒逃過停止所有使用者線程的宿命,隻是用多線程快些,

使用者感覺的 GC 停頓小些,使用者體驗好些。

3、應用場景

在 Server 模式下,ParNew 收集器是一個非常重要的收集器,因為除 Serial 外,目前隻有它能

與 CMS 收集器配合工作;CMS 下面會介紹,它是 HotSpot 在 JDK1.5 推出的第一款真正

意義上的并發(Concurrent)收集器,它第一次實作了讓垃圾線程和使用者線程同時工作。

CMS 作為老年代收集器,但卻無法與 JDK1.4 已經存在的新生代收集器 Parallel Scavenge

配合工作;因為 Parallel Scavenge(以及G1)都沒有使用傳統的 GC 收集器代碼架構,

而另外獨立實作;而其餘幾種收集器則共用了部分的架構代碼;

同時,在單個 CPU 環境中,ParNew 收集器不會比 Serail 收集器有更好的效果,因為存在

線程互動開銷,這是多線程避免不了的線程上下文切換開銷,但是在多個 CPU 下,

Serial 跟 ParNew 沒法相比,現在是多核時代,"榨幹" CPU 來提高運作效率是程式追求的,

是以 ParNew 配合 CMS 成為不二選擇。

4、參數設定

"-XX:+UseConcMarkSweepGC":指定使用 CMS 後,會預設使用 ParNew 作為新生代收集器;

"-XX:+UseParNewGC":強制指定使用 ParNew;    

"-XX:ParallelGCThreads":指定垃圾收集的線程數量,ParNew 預設開啟的收集線程與 CPU

的數量相同;

四 Parallel Scavenge收集器

Parallel Scavenge 收集器是一個新生代收集器,它也是用複制算法、同時也是多線程收集器,

看上去和 ParNew 一樣,但是它關注的點與其他收集器不同,别的收集器主要關心如何

縮短 GC 停頓時間,而 Parallel Scavenge 關心的是吞吐量,是以 Parallel Scavenge

也稱為"吞吐量優先"收集器。

1、特性

1)有些特點與 ParNew 相似,比如針對新生代收集,采用複制算法,多線程收集。

2)别的收集器主要關注 GC 停頓時間,而 Parallel Scavenge 主要目标是達到一個

可控制的吞吐量。

2、應用場景

停頓時間越短就越适合需要與使用者互動的程式,良好的響應速度能提升使用者體驗,

而高吞吐量則可以高效率地利用 CPU 時間,盡快完成程式的運算任務,主要适合在

背景運算而不需要太多互動的任務。例如:長時間處理大資料,科學計算等等。

3、基本原理

Parallel Scavenge/Parallel Old 收集器工作過程示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

4、參數設定

Parallel Scavenge 收集器提供兩個參數用于精确控制吞吐量,分别是控制最大垃圾收集

停頓時間的"-XX:MaxGCPauseMillis"參數以及直接設定吞吐量大小的"-XX:GCTimeRatio"參數。

"-XX:MaxGCPauseMillis" 控制最大垃圾收集停頓時間參數允許的值是一個大于 0 的毫秒數,

收集器将盡可能地保證記憶體回收花費的時間不超過設定值;

MaxGCPauseMillis 設定得稍小,停頓時間可能會縮短,但也可能會使得吞吐量下降,

因為可能導緻垃圾收集發生得更頻繁;

"-XX:GCTimeRatio" 設定垃圾收集時間占總時間的比率,該值範圍為 0<n<100 的整數;

GCTimeRatio 相當于設定吞吐量大小;

垃圾收集執行時間占應用程式執行時間的比例的計算方法是:1 / (1 + n)

例如,選項-XX:GCTimeRatio=19,設定了垃圾收集時間占總時間的5%--1/(1+19);

預設值是1%--1/(1+99),即n=99;

垃圾收集所花費的時間是年輕一代和老年代收集的總時間;

如果沒有滿足吞吐量目标,則增加代的記憶體大小以盡量增加使用者程式運作的時間;

還有一個參數,"-XX:+UseAdptiveSizePolicy"

開啟這個參數後,就不用手工指定一些細節參數,如:

新生代的大小(-Xmn)、Eden 與 Survivor 區的比例(-XX:SurvivorRation)、

晉升老年代的對象年齡(-XX:PretenureSizeThreshold)等;

JVM 會根據目前系統運作情況收集性能監控資訊,動态調整這些參數,以提供最合适的

停頓時間或最大的吞吐量,這種調節方式稱為 GC 自适應的調節政策(GC Ergonomiscs);    

當你不知道怎麼優化的時候,自适應調節政策是一種值得推薦的方式:

隻需設定好記憶體資料大小(如"-Xmx"設定最大堆);

然後使用 "-XX:MaxGCPauseMillis" 或 "-XX:GCTimeRatio" 給 JVM 設定一個優化目标;

那些具體細節參數的調節就由 JVM 自适應完成;

這也是 Parallel Scavenge 收集器與 ParNew 收集器一個重要差別;

五 Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,它同樣是一個單線程收集器,使用"标記-整理"算法。

1、特性

1)針對老年代進行收集;

2)采用"标記-整理"算法;

3)是一個單線程收集器;

2、基本原理

Serial/Serial Old 收集器運作示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

3、應用場景

主要用于 Client 模式;

而在 Server 模式有兩大用途:

1)在 JDK1.5 及之前,與 Parallel Scavenge 收集器搭配使用(JDK1.6有 Parallel Old 收集器

可搭配);

2)作為 CMS 收集器的後備預案,在并發收集發生 Concurrent Mode Failure 時使用;

六 Parallel Old 收集器

Parallel Old 是 Parallel Scagenge 收集器的老年代版本,使用多線程和"标記-整理"算法。

1、特性

1)針對老年代收集;

2)"标記-整理"算法;

3)多線程收集;

2、基本原理

Parallel Scavenge/Parallel Old 收集器工作過程示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

3、應用場景

Parallel Old 收集器是在 JDK1.6 中才開始提供的,用于替代老年代 Serial Old 收集器。

因為 Serial Old 收集器在服務端應用性能上是一個"拖累"。

特别是在 Server 模式,多 CPU 的情況下;這樣在注重吞吐量以及 CPU 資源敏感的場景,

就有了 Parallel Scavenge 加 Parallel Old 收集器的"給力"應用組合;

4、參數設定

"-XX:+UseParallelOldGC":指定使用 Parallel Old 收集器;

七 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以 擷取最短回收停頓時間為目的的收集器,

也稱為并發低停頓收集器(Concurrent Low Pause Collector)或低延遲(low-latency)垃圾收集器;

1、特性

1)正對老年代收集;

2)采用"标記-清除"算法;

3)以擷取最短回收停頓時間為目标;

4)并發收集、低停頓;

5)需要更多的記憶體;

2、基本原理

CMS 收集器收集過程分為四個步驟:

1)初始化标記

僅标記一下 GC Roots 能直接關聯到的對象;

速度很快;

但需要"Stop The World";

2)并發标記

進行 GC Roots Tracing 的過程;

剛才産生的集合中标記出存活對象;

應用程式也在運作;

并不能保證可以标記出所有的存活對象;

3)重新标記

為了修正并發标記期間因使用者程式繼續運作而導緻标記變動的那一部分對象的标記記錄;

需要"Stop The World",且停頓時間比初始标記稍長,但遠比并發标記短;

采用多線程并行執行來提升效率;

4)并發清除标記

回收所有的垃圾對象;

其中初始标記和重新标記這兩個步驟仍然需要"Stop The Word"。整個過程中耗時最長的

并發标記和并發清除都可以與使用者線程一起工作;是以總體上說,CMS 收集器的記憶體

回收過程與使用者線程一起并發執行;

CMS 收集器運作示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

3、CMS 三個缺點

1)CMS 收集器對 CPU 資源非常敏感。

并發收集雖然不會暫停使用者線程,但因為占用一部分 CPU 資源,還是會導緻應用程式變慢,

總吞吐量降低。CMS 的預設收集線程數量是=(CPU數量+3)/4;當 CPU 數量多于 4 個,

收集線程占用的 CPU 資源多于 25%,對使用者程式影響可能較大;不足 4 個時,影響更大,

可能無法接受。

2)CMS 收集器無法處理浮動垃圾(Floating Garbage),可能出現 "Concurrent Mode Failure"失

敗而導緻一次 Full GC 的産生。

浮動垃圾(Floating Garbage)

在并發清除時,使用者線程新産生的垃圾,稱為浮動垃圾;這使得并發清除時需要預留一定的

記憶體空間,不能像其他收集器在老年代幾乎填滿再進行收集;也要可以認為 CMS 所需要的

空間比其他垃圾收集器大;

"-XX:CMSInitiatingOccupancyFraction":設定 CMS 預留記憶體空間;

JDK1.5 預設值為 68%;JDK1.6 變為大約 92%;               

"Concurrent Mode Failure"失敗

如果 CMS 預留記憶體空間無法滿足程式需要,就會出現一次"Concurrent Mode Failure"失敗;

這時JVM啟用後備預案:臨時啟用 Serail Old 收集器,而導緻另一次 Full GC 的産生;

這樣的代價是很大的,是以 CMSInitiatingOccupancyFraction 不能設定得太大。

3)産生大量的記憶體碎片

由于 CMS 基于"标記-清除"算法,清除後不進行壓縮操作;

産生大量不連續的記憶體碎片會導緻配置設定大記憶體對象時,無法找到足夠的連續記憶體,

進而需要提前觸發另一次 Full GC 動作。

記憶體碎片解決方法:                

"-XX:+UseCMSCompactAtFullCollection"

使得 CMS 出現上面這種情況時不進行 Full GC,而開啟記憶體碎片的合并整理過程;

但合并整理過程無法并發,停頓時間會變長;

預設開啟(但不會進行,結合下面的CMSFullGCsBeforeCompaction);

"-XX:+CMSFullGCsBeforeCompaction"

設定執行多少次不壓縮的 Full GC 後,來一次壓縮整理;

為減少合并整理過程的停頓時間;

預設為 0,也就是說每次都執行 Full GC,不會進行壓縮整理;

由于空間不再連續,CMS 需要使用可用"空閑清單"記憶體配置設定方式,這比簡單實用"碰撞指針"

配置設定記憶體消耗大;

八 G1收集器

G1(Garbage-First)是面向服務端應用,JDK7-u4 才推出商用的收集器,是當今收集器

技術發展的最前沿成果之一;

1、特性

1)并行與并發

能充分利用多 CPU、多核環境下的硬體優勢,使用多個 CPU(CPU或CPU核心)來

縮短"Stop The World"停頓時間;

也可以并發讓垃圾收集與使用者程式同時進行;

2)分代收集,收集範圍包括新生代和老年代    

能獨立管理整個 GC 堆(新生代和老年代),而不需要與其他收集器搭配;

能夠采用不同方式處理不同時期的對象以獲得更好的收集效果;

雖然保留分代概念,但 Java 堆的記憶體布局有很大差别;将整個堆劃分為多個大小相等的

獨立區域(Region);

新生代和老年代不再是實體隔離,它們都是一部分 Region(不需要連續)的集合;

3)空間整合

從整體看,是基于标記-整理算法實作的收集器,從局部(兩個Region間)看,

是基于"複制"算法實作的;

運作期間都不會産生記憶體碎片,收集後能提供規整的可用記憶體,有利于長時間運作,

配置設定大對象是不會因為無法找到連續記憶體空間而提前觸發一下GC;

4)可預測的停頓:低停頓的同時實作高吞吐量

G1 除了追求低停頓處,還能建立可預測的停頓時間模型,可以明确指定 M 毫秒時間片内,

垃圾收集消耗的時間不超過 N 毫秒;

2、基本原理

    不計算維護Remembered Set的操作,可以分為4個步驟(與CMS較為相似)。

1)初始标記(Initial Marking)

僅标記一下 GC Roots 能直接關聯到的對象;

且修改 TAMS(Next Top at Mark Start),讓下一階段并發運作時,使用者程式能在正确可用的

Region 中建立新對象;

需要"Stop The World",但速度很快;

2)并發标記(Concurrent Marking)

進行 GC Roots Tracing 的過程;

剛才産生的集合中标記出存活對象;

耗時較長,但應用程式也在運作;

并不能保證可以标記出所有的存活對象;

3)最終标記(Final Marking)

為了修正并發标記期間因使用者程式繼續運作而導緻标記變動的那一部分對象的标記記錄;

上一階段對象的變化記錄線上程的 Remembered Set Log;

這裡把 Remembered Set Log 合并到 Remembered Set 中;

需要"Stop The World",且停頓時間比初始标記稍長,但遠比并發标記短;

采用多線程并行執行來提升效率;

4)篩選回收(Live Data Counting and Evacuation)

首先排序各個 Region 的回收價值和成本;

然後根據使用者期望的 GC 停頓時間來制定回收計劃;

最後按計劃回收一些價值高的 Region 中垃圾對象;

回收時采用"複制"算法,從一個或多個 Region 複制存活對象到堆上的另一個空的 Region,

并且在此過程中壓縮和釋放記憶體;可以并發進行,降低停頓時間,并增加吞吐量;

G1 收集器運作示意圖:

【JVM原理】垃圾回收機制(3)--垃圾收集器前言一 垃圾收集器概述二 Serial收集器三 ParNew收集器四 Parallel Scavenge收集器五 Serial Old 收集器六 Parallel Old 收集器七 CMS收集器八 G1收集器參考文獻

全堆收集,多種算法結合,與使用者線程并行進行。

3、應用場景

面向服務端應用,針對具有大記憶體、多處理器的機器;

最主要的應用是為需要低 GC 延遲,并具有大堆的應用程式提供解決方案;

如:在堆大小約 6GB 或更大時,可預測的暫停時間可以低于 0.5 秒;

用來替換掉 JDK1.5 中的 CMS 收集器;

在下面的情況時,使用 G1 可能比 CMS 好:

1)超過 50% 的 Java 堆被活動資料占用;

2)對象配置設定頻率或年代提升頻率變化很大;

3)GC 停頓時間過長(長于 0.5 至 1 秒)。

是否一定采用 G1 呢?也未必:

如果現在采用的收集器沒有出現問題,不用急着去選擇 G1;

如果應用程式追求低停頓,可以嘗試選擇 G1;

是否代替 CMS 需要實際場景測試才知道。

4、參數設定

"-XX:+UseG1GC":指定使用 G1 收集器;

"-XX:InitiatingHeapOccupancyPercent":當整個Java堆的占用率達到參數值時,

開始并發标記階段;預設為 45;

"-XX:MaxGCPauseMillis":為 G1 設定暫停時間目标,預設值為 200 毫秒;

"-XX:G1HeapRegionSize":設定每個 Region 大小,範圍 1MB 到 32MB;

目标是在最小 Java 堆時可以擁有約 2048 個 Region;

到這裡,HotSpot 虛拟機中收集器大概都了解了一遍。

參考文獻

《深入了解Java虛拟機》 (第二版) 周志明 著;