在經(jīng)過(guò)了幾次跳票之后,Java 9終于在原計(jì)劃日期的整整一年之后發(fā)布了正式版。Java 9引入了很多新的特性,除了閃瞎眼的Module System和REPL,最重要的變化我認(rèn)為是默認(rèn)GC(Garbage Collector)修改為新一代更復(fù)雜、更全面、性能更好的G1(Garbage-First)。JDK的維護(hù)者在GC選擇上一直是比較保守的,G1從JDK 1.6時(shí)代就開(kāi)始進(jìn)入開(kāi)發(fā)者的視野,直到今天正式成為Hotspot的默認(rèn)GC,也是走了很長(zhǎng)的路。
本文將主要講解GC調(diào)優(yōu)需要知道的一些基礎(chǔ)知識(shí),會(huì)涉及到一些GC的實(shí)現(xiàn)細(xì)節(jié),但不會(huì)對(duì)實(shí)現(xiàn)細(xì)節(jié)做很全面的闡述,如果你看完本文之后,能對(duì)GC有一個(gè)大致的認(rèn)識(shí),那本文的寫作目的也就達(dá)到了。由于在這次寫作過(guò)程中,恰逢Java 9正式版發(fā)布,之前都是依賴Java 8的文檔寫的,如果有不正確的地方還望指正。本文將包含以下內(nèi)容:
- GC的作用范圍
- GC負(fù)責(zé)的事情
- JVM中的4種GC
- G1的一些細(xì)節(jié)
- 使用Java 9正式版對(duì)G1進(jìn)行測(cè)試
- 一些簡(jiǎn)單的GC調(diào)優(yōu)方法
一、GC的作用范圍
要談GC的作用范圍,首先要談JVM的內(nèi)存結(jié)構(gòu),JVM內(nèi)存中主要有以下幾個(gè)區(qū)域:堆、方法區(qū)(JVM規(guī)范中的叫法,Hotspot大致對(duì)應(yīng)的是Metaspace)、棧、本地方法棧、PC等,其中GC主要作用在堆上,如下圖所示:
其中堆和方法區(qū)是所有線程共享的,其他則為線程獨(dú)有,HotSpot JVM使用基于分代的垃圾回收機(jī)制,所以在堆上又分為幾個(gè)不同的區(qū)域(在G1中,各年齡代不再是連續(xù)的一整片內(nèi)存,為了描述方便,這里還使用傳統(tǒng)的表示方法),具體如下圖所示:
二、GC負(fù)責(zé)的事情
GC的發(fā)展是隨著JDK(Standard Edition)的發(fā)展一步步發(fā)展起來(lái)的,垃圾回收(Garbage Collection)可以說(shuō)是JDK里最影響性能的行為了。GC做的事情,說(shuō)白了就是「通過(guò)對(duì)內(nèi)存進(jìn)行管理,以保障在內(nèi)存足夠的時(shí)候,程序可以正常的使用內(nèi)存」。具體而言,GC通常做的事情有以下3個(gè):
1. 分配對(duì)象和對(duì)象的年齡管理
通常而言,GC需要管理「在上圖中的年輕代(Young)分配對(duì)象,然后通過(guò)一系列的年齡管理,將之銷毀或晉升到老年代(Tenured)中去」的過(guò)程。這個(gè)過(guò)程會(huì)伴隨著若干次的Minor GC。
對(duì)于普通的對(duì)象而言,分配內(nèi)存是一件很簡(jiǎn)單而且快速的事情。在對(duì)象還未創(chuàng)建時(shí),其所占內(nèi)存大小通過(guò)類的元數(shù)據(jù)就可以確定,而Eden區(qū)域的內(nèi)存可以認(rèn)為是連續(xù)的,所以給對(duì)象分配內(nèi)存要做的只是在上圖中Eden區(qū)域中把指針移動(dòng)相應(yīng)的長(zhǎng)度,并將地址返回給對(duì)象的引用即可。當(dāng)然實(shí)際的過(guò)程比這個(gè)復(fù)雜,在下文中會(huì)提到。
不過(guò),有時(shí)候一個(gè)對(duì)象會(huì)直接在老年代中創(chuàng)建,這個(gè)點(diǎn)也會(huì)在后邊提到。
2. 在老年代中進(jìn)行標(biāo)記
老年代的GC算法可以大致是認(rèn)為是一個(gè)標(biāo)記-整理(Mark-Compact,其實(shí)是混合了標(biāo)記-清理,標(biāo)記-復(fù)制和標(biāo)記-整理)算法,所以老年代的垃圾清理首先要做的就是在老年代對(duì)存活的對(duì)象(可達(dá)性分析,關(guān)于不同的可達(dá)性可以參考JDK解構(gòu) - Java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn))進(jìn)行標(biāo)記,對(duì)于尋求大吞吐量的服務(wù)器應(yīng)用來(lái)說(shuō),這個(gè)過(guò)程往往需要是并發(fā)的。
標(biāo)記的過(guò)程發(fā)生在Major GC被觸發(fā)之后,不同的GC對(duì)于MajorGC的觸發(fā)條件和標(biāo)記過(guò)程的實(shí)現(xiàn)也不盡相同。
3. 在老年代中進(jìn)行壓縮
在上一條的基礎(chǔ)上,將還存活的對(duì)象進(jìn)行壓縮(CMS和G1的行為與此有些不同之處),壓縮的過(guò)程就是將存活的對(duì)象從老年代的起點(diǎn)進(jìn)行挨個(gè)復(fù)制,使得老年代維持在一片連續(xù)的內(nèi)存中,消除內(nèi)存碎片,對(duì)于內(nèi)存分配速度的提升會(huì)有很大的幫助。
三、GC的種類
Hotspot會(huì)根據(jù)宿主機(jī)的硬件特性和操作系統(tǒng)類型,將之分為客戶端型(client-class)或者服務(wù)器型(server-class),如果是服務(wù)器型主機(jī),Java 9之前默認(rèn)使用Parallel GC,Java 9中默認(rèn)使用G1。對(duì)于服務(wù)器型主機(jī)的選擇標(biāo)準(zhǔn)是「CPU核心數(shù)大于1,內(nèi)存大于2GB」,所以現(xiàn)在大部分的主機(jī)都可以認(rèn)為是服務(wù)器型主機(jī)。
這里討論的所有GC都是基于分代垃圾回收算法的。
1. Serail
Serail是最早的一款GC,它只使用一個(gè)線程來(lái)做所有的Minor和Major垃圾回收。它在運(yùn)行時(shí),其他所有的事情都會(huì)暫停。其工作方式十分簡(jiǎn)單,在需要GC的安全點(diǎn),它會(huì)停止所有其他線程(Stop-The-World),對(duì)年輕代進(jìn)行標(biāo)記-復(fù)制,或?qū)夏甏M(jìn)行標(biāo)記-整理。
可以使用JVM參數(shù)-XX:+UseSerialGC
來(lái)開(kāi)啟此GC,當(dāng)使用此參數(shù)時(shí),年輕代和老年代將都是用Serial來(lái)做垃圾回收。在年輕代使用標(biāo)記-復(fù)制算法,將Eden中存活的對(duì)象和非空的Suvivor區(qū)(From)中存活的對(duì)象復(fù)制到空的Suvivor區(qū)(To)中去,同時(shí)將一部分Suvivor中的對(duì)象晉升到老年代去。在老年代則使用標(biāo)記-整理算法。
看起來(lái)Serial古老而簡(jiǎn)陋,但在宿主機(jī)資源緊張或者JVM堆很小的情況下(比如堆內(nèi)存大小只有不到100M),Serial反而可以達(dá)到更好的效果,因?yàn)槠渌l(fā)或并行GC都是基于多線程的,會(huì)帶來(lái)額外的線程切換和線程間通信的開(kāi)銷。
2. Parallel/Throughput
Parallel在Java 9之前是服務(wù)器型宿主機(jī)中JVM的默認(rèn)GC,其垃圾回收的算法和Serial基本相同,不同之處在與它使用多線程來(lái)執(zhí)行。由于使用了多線程,可以享受多核CPU帶來(lái)的優(yōu)勢(shì),可以通過(guò)參數(shù)-XX:+UseParallelGC -XX:+UseParallelOldGC顯示指定。
3. CMS
CMS和G1都屬于「Mostly Concurrent Mark and Sweep Garbage Collector」,可以使用-XX:+UseConcMarkSweepGC參數(shù)打開(kāi)。CMS的年輕代垃圾回收使用的是Parallel New
來(lái)做,其行為和Parallel中的差不多相同,他們的實(shí)現(xiàn)上有一些不同的地方,比如Parallel可以自動(dòng)調(diào)節(jié)年輕代中各區(qū)的大小,用的是廣度優(yōu)先搜索等。
老年代使用CMS,CMS的回收和Parallel也基本類似,不同點(diǎn)在與,CMS使用的更復(fù)雜的可達(dá)性分析步驟,并且不是每次都做壓縮的動(dòng)作,這樣達(dá)到的效果就是,Stop-The-World的時(shí)長(zhǎng)會(huì)降低,JVM運(yùn)行中斷的時(shí)間減少,適合在對(duì)延遲敏感的場(chǎng)景下使用。
CMS在Java 9中已經(jīng)被廢棄,但了解CMS的行為對(duì)理解G1會(huì)有一些幫助,所以這里還是會(huì)簡(jiǎn)單的敘述一下。CMS的步驟大致如下:
第一次標(biāo)記
從GC Roots開(kāi)始,找到它們?cè)诶夏甏械谝粋€(gè)可達(dá)的對(duì)象,這些對(duì)象或者是直接被GC Roots引用,或者通過(guò)年輕代中的對(duì)象被GC Roots引用。這一步會(huì)Stop-The-World。并發(fā)標(biāo)記
在第一次標(biāo)記的基礎(chǔ)上,進(jìn)一步進(jìn)行可達(dá)性分析,從而標(biāo)記存活的對(duì)象。這一步叫「并發(fā)」標(biāo)記,是因?yàn)樽鰳?biāo)記的線程是和應(yīng)用的工作線程并發(fā)執(zhí)行的,也就是說(shuō),這一步不會(huì)Stop-The-World。第二次標(biāo)記
在并發(fā)標(biāo)記的過(guò)程中,由于程序仍在執(zhí)行,會(huì)導(dǎo)致在并發(fā)標(biāo)記完成后,有一些對(duì)象的可達(dá)性會(huì)發(fā)生變化,所以需要再次對(duì)他們進(jìn)行標(biāo)記。這一步會(huì)Stop-The-World。清理
回收不使用的對(duì)象,留作以后使用。
CMS的設(shè)計(jì)比較復(fù)雜,所以也帶來(lái)了一些問(wèn)題,比如浮動(dòng)垃圾(Floating Garbage,指的是在第一步標(biāo)記可達(dá),但在第二步執(zhí)行的同時(shí)已經(jīng)不可達(dá)的對(duì)象),由于不做老年代壓縮,導(dǎo)致老年代會(huì)出現(xiàn)較多的內(nèi)存碎片。
4. G1
由于「引入了并發(fā)標(biāo)記」和「不做老年代壓縮」,CMS可以帶來(lái)更好的響應(yīng)時(shí)延表現(xiàn),但同時(shí)也帶來(lái)了一些問(wèn)題。G1本身就是作為CMS的替代品出現(xiàn)的,在它的使用場(chǎng)景里,堆不再是連續(xù)的被分為上文所說(shuō)的各種代,整個(gè)堆會(huì)被分為一個(gè)個(gè)區(qū)域(Region),每個(gè)區(qū)域可以是任何代。如下圖所示:
其中有紅色方框的為年輕代(標(biāo)S的為Survivor區(qū)域,其他為Eden),其他藍(lán)色底的區(qū)域?yàn)槔夏甏?biāo)H的為大對(duì)象區(qū)域,用以存儲(chǔ)大對(duì)象)。
四、G1的一些細(xì)節(jié)
G1與以上3種GC相同,也是基于分代的垃圾回收器。它的垃圾回收步驟可以分為年輕代回收(Young-only phase,類似于Minor GC)和混合垃圾回收階段(Space-reclamation phase)。下圖是Oracle文檔中對(duì)于此兩個(gè)階段的示意圖:
G1設(shè)計(jì)目標(biāo)和適用對(duì)象
G1的設(shè)計(jì)目標(biāo)是讓大型的JVM可以動(dòng)態(tài)的控制GC的行為以滿足用戶配置的性能目標(biāo)。G1會(huì)在平衡吞吐和響應(yīng)時(shí)延的基礎(chǔ)上,盡可能的滿足用戶的需求。它適用的JVM往往有以下特征:
- 堆的大小可能達(dá)到數(shù)十G(或者更大),同時(shí)存活的對(duì)象數(shù)量也很多。
- 對(duì)象的分配和年齡增長(zhǎng)的行為隨著程序的運(yùn)行不斷的變化
- 堆上很容易形成碎片
- 要求較少的Stop-The-World暫停時(shí)間,通常小于數(shù)百毫秒
對(duì)G1的行為進(jìn)行測(cè)試
如果想要看垃圾回收的具體執(zhí)行過(guò)程,可以使用虛擬機(jī)參數(shù)-Xlog:gc*=debug
或者-Xlog:gc*=info
,前一個(gè)會(huì)打印更多的細(xì)節(jié)。注意傳統(tǒng)的VM參數(shù)-XX:+PrintGCDetails
在Java9中已經(jīng)廢棄,會(huì)有Warning信息。可以使用以下代碼中的程序去測(cè)試:
static int TOTAL_SIZE = 1024 * 5;
static Object[] floatingObjs= new Object[TOTAL_SIZE];
static LinkedList<Object> immortalObjs = new LinkedList<Object>();
//釋放浮動(dòng)垃圾
synchronized static void renewFloatingObjs() {
System.err.println("存活對(duì)象滿========================================");
if (floatingSize + 5 >= TOTAL_SIZE) {
floatingObjs= new Object[TOTAL_SIZE];
floatingSize = 0;
}
}
//添加浮動(dòng)垃圾
synchronized static void addObjToFloating(Object obj) {
if (floatingSize++ < TOTAL_SIZE) {
floatingObjs[floatingSize] = obj;
if (immortalSize++ < TOTAL_SIZE) {
immortalObjs.add(obj);
} else {
immortalObjs.remove(new Random().nextInt(TOTAL_SIZE));
immortalObjs.add(obj);
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Byte[] garbage = new Byte[1024 * (1 + new Random().nextInt(20))];
if (new Random().nextInt(20) < 2) {
if (floatingSize + 5 >= TOTAL_SIZE) {
renewFloatingObjs();
}
addObjToFloating(garbage);
}
}
}).start();
}
}
在這段代碼中,模擬了常規(guī)程序的使用情況。不斷的生成新的大小不等的對(duì)象,這些對(duì)象中會(huì)有大約10%的機(jī)會(huì)進(jìn)入浮動(dòng)垃圾floatingObjs
,浮動(dòng)垃圾會(huì)被定期清除。同時(shí)會(huì)有一部分的對(duì)象進(jìn)入immortalObjs
,這些對(duì)象被釋放的機(jī)會(huì)更少,它們大概率將成為老年代的常住用戶。
從上邊的測(cè)試可以得到如下GC日志1,這是一次完整的年輕代GC,從中可以看到,默認(rèn)的區(qū)域大小為1M,同時(shí)將開(kāi)始一次Full GC
,其格式大致為[<虛擬機(jī)運(yùn)行的時(shí)長(zhǎng)>][<日志級(jí)別>][<標(biāo)簽>] GC(<GC的標(biāo)識(shí)>) <其他信息>
//日志1
[0.014s][info][gc,heap] Heap region size: 1M
//一次完整的年輕代垃圾回收,伴隨著一次暫停
[12.059s][info ][gc,start ] GC(18) Pause Young (G1 Evacuation Pause)
[12.059s][info ][gc,task ] GC(18) Using 8 workers of 8 for evacuation
[12.078s][info ][gc,phases ] GC(18) Pre Evacuate Collection Set: 0.0ms
[12.078s][info ][gc,phases ] GC(18) Evacuate Collection Set: 18.6ms
[12.079s][info ][gc,phases ] GC(18) Post Evacuate Collection Set: 0.3ms
[12.079s][info ][gc,phases ] GC(18) Other: 0.3ms
[12.079s][info ][gc,heap ] GC(18) Eden regions: 342->0(315)
[12.079s][info ][gc,heap ] GC(18) Survivor regions: 38->35(48)
[12.079s][info ][gc,heap ] GC(18) Old regions: 425->463
[12.079s][info ][gc,heap ] GC(18) Humongous regions: 0->0
[12.078s][debug][gc,ergo,ihop ] GC(18) Request concurrent cycle initiation (occupancy higher than threshold) occupancy: 485490688B allocation request: 0B threshold: 472331059B (45.00) source: end of GC
[12.078s][debug][gc,ihop ] GC(18) Basic information (value update), threshold: 472331059B (45.00), target occupancy: 1049624576B, current occupancy: 521069456B, recent allocation size: 20640B, recent allocation duration: 817.38ms, recent old gen allocation rate: 25251.50B/s, recent marking phase length: 0.00ms
[12.078s][debug][gc,ihop ] GC(18) Adaptive IHOP information (value update), threshold: 472331059B (47.37), internal target occupancy: 997143347B, occupancy: 521069456B, additional buffer size: 367001600B, predicted old gen allocation rate: 318128.08B/s, predicted marking phase length: 0.00ms, prediction active: false
[12.078s][debug][gc,ergo,refine ] GC(18) Updated Refinement Zones: green: 15, yellow: 45, red: 75
[12.079s][info ][gc,heap ] GC(18) Eden regions: 342->0(315)
[12.079s][info ][gc,heap ] GC(18) Survivor regions: 38->35(48)
[12.079s][info ][gc,heap ] GC(18) Old regions: 425->463
[12.079s][info ][gc,heap ] GC(18) Humongous regions: 0->0
[12.079s][info ][gc,metaspace ] GC(18) Metaspace: 5172K->5172K(1056768K)
[12.079s][debug][gc,heap ] GC(18) Heap after GC invocations=19 (full 0):
[12.079s][info ][gc ] GC(18) Pause Young (G1 Evacuation Pause) 803M->496M(1001M) 19.391ms
[12.079s][info ][gc,cpu ] GC(18) User=0.05s Sys=0.00s Real=0.02s
年輕代回收(Young-only)
對(duì)于純粹的年輕代回收,其算法很簡(jiǎn)單,與Parallel和CMS的年輕代十分類似,這是一個(gè)多線程并行執(zhí)行的過(guò)程,同樣需要Stop-The-World(對(duì)應(yīng)上邊日志中的Pause Young
),停下來(lái)所有的工作線程,然后將Eden上存活的對(duì)象拷貝到Suvivor區(qū)域,這里會(huì)將很多個(gè)對(duì)象從多個(gè)不同的區(qū)域拷貝到少數(shù)的幾個(gè)區(qū)域內(nèi),所以這一步在G1中叫做疏散(Evacuation),同時(shí)把Suvivor上觸及年齡閾值的對(duì)象晉升到老年代區(qū)域。
老年代回收(concurrent cycle)
G1的老年代回收是在老年代空間觸及一個(gè)閾值(Initiating Heap Occupancy Percent)之后,這個(gè)回收伴隨著年輕代的回收工作,但與上邊所說(shuō)的回收有些不同。
-
年輕代回收:伴隨著年輕代的回收工作,同時(shí)會(huì)執(zhí)行并發(fā)標(biāo)記和一部分清理的工作,這樣可以共用年輕代垃圾回收的Stop-The-World。
第一次標(biāo)記:對(duì)應(yīng)一次
Pause Initial Mark
和CMS的步驟類似,首先進(jìn)行第一次標(biāo)記。但實(shí)現(xiàn)方法上有很大的區(qū)別,G1首先對(duì)當(dāng)前堆上的對(duì)象情況進(jìn)行一個(gè)虛擬快照(Snapshot-At-The-Beginning),然后根據(jù)這個(gè)快照對(duì)老年代的對(duì)象和區(qū)域進(jìn)行標(biāo)記,并執(zhí)行之后的垃圾回收。之后像CMS一樣會(huì)有并發(fā)標(biāo)記的過(guò)程。
這樣會(huì)產(chǎn)生一個(gè)問(wèn)題,在這次回收結(jié)束之后,會(huì)有些對(duì)象在并發(fā)標(biāo)記的過(guò)程中,它的可達(dá)性已經(jīng)變化,導(dǎo)致已經(jīng)不可達(dá)的對(duì)象仍然沒(méi)有被回收。但是這樣能帶來(lái)更好的響應(yīng)時(shí)間。重新標(biāo)記:對(duì)應(yīng)一次
Pause Remark
在這個(gè)階段,G1首先完成上一步開(kāi)始的標(biāo)記工作,之后會(huì)對(duì)特殊引用的對(duì)象進(jìn)行處理(具體可以參考JDK解構(gòu) - Java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn)),還有對(duì)Metaspace區(qū)域進(jìn)行垃圾回收。這一步會(huì)進(jìn)行Stop-The-World。清理:對(duì)應(yīng)一次
Pause Cleanup
這一步主要做的是收集當(dāng)前堆中的內(nèi)存區(qū)域信息,對(duì)空的區(qū)域進(jìn)行回收,為接下來(lái)的空間回收做一些準(zhǔn)備工作,清理結(jié)束之后,通常會(huì)伴隨著一次年輕代回收,如果判斷不需要進(jìn)行空間回收,則會(huì)進(jìn)入下一個(gè)年輕代回收的工作。這一步會(huì)進(jìn)行Stop-The-World。
混合垃圾回收:對(duì)應(yīng)一次或多次
Pause Mixed
主要做的是對(duì)老年代的區(qū)域內(nèi)存進(jìn)行疏散(Evacuation),也包含對(duì)年輕代的區(qū)域回收工作。同時(shí)這一步也會(huì)動(dòng)態(tài)地調(diào)整IHOP
從對(duì)G1的GC日志的分析,可以看到G1的垃圾回收行為是基于一個(gè)可預(yù)測(cè)的模型:GC會(huì)不斷的主動(dòng)觸發(fā)垃圾回收,在這個(gè)過(guò)程中不斷地進(jìn)行信息統(tǒng)計(jì)和系統(tǒng)GC參數(shù)的設(shè)置,然后將上邊這些步驟安排在這些垃圾回收過(guò)程中。
大對(duì)象的分配
正常情況下,一個(gè)對(duì)象會(huì)在年輕代的Eden中創(chuàng)建,然后通過(guò)垃圾回收和年齡管理之后,晉升到老年代。但對(duì)于某些比較大的對(duì)象,可能會(huì)直接分配到老年代去。
對(duì)于G1,對(duì)象大多數(shù)情況都會(huì)在Eden上分配,如果JVM判斷一個(gè)對(duì)象為大對(duì)象(其閾值可以通過(guò)-XX:G1HeapRegionSize來(lái)設(shè)置),則會(huì)直接分配如老年代的大對(duì)象區(qū)域中。
對(duì)于其他的內(nèi)存區(qū)域連續(xù)的GC,下面是從StackOverflow上搬運(yùn)過(guò)來(lái)的對(duì)象在堆上的分配過(guò)程:
使用 thread local allocation buffer (TLAB), 如果空間足夠,則分配成功。
從名稱便可知,TLAB是線程獨(dú)占的,所以線程安全,且速度非常快。如果一個(gè)TLAB滿了,線程會(huì)被分配一個(gè)新的TLAB。如果TLAB 空間不夠這次分配對(duì)象,但其中還有很多空間可用,則不使用TLAB,直接在Eden中分配對(duì)象。
直接在Eden上分配對(duì)象要去搶占Eden中的指針操作,其代價(jià)較使用TLAB要大一些。如果Eden的對(duì)象分配失敗,出發(fā)Minor GC。
如果Minor GC完成后還不夠,則直接分配到老年代。
一些簡(jiǎn)單的GC調(diào)優(yōu)方法
1. 使用不同的索引對(duì)象
引用的類型會(huì)直接影響其所引用對(duì)象的GC行為,當(dāng)要做一些內(nèi)存敏感的應(yīng)用時(shí),可以參考使用合適的引用類型。具體可以參考JDK解構(gòu) - Java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn)。
2. 使用Parallel
從上文中可知,Java 8默認(rèn)的GC是Parallel,它也叫Throughput,所以它的目的是盡可能的增加系統(tǒng)的吞吐量。在Parallel里,可以通過(guò)參數(shù)調(diào)節(jié)最大停止時(shí)間(-XX:MaxGCPauseMillis,默認(rèn)無(wú)設(shè)置)和吞吐量(-XX:GCTimeRatio,默認(rèn)值是99,即最大使用1%的時(shí)間來(lái)做垃圾回收)來(lái)調(diào)優(yōu)GC的行為。其中設(shè)置最大停止時(shí)間可能會(huì)導(dǎo)致GC調(diào)節(jié)各年齡代分區(qū)的尺寸(通過(guò)增量來(lái)實(shí)現(xiàn))。
3. 使用G1
從Java 9開(kāi)始G1變成了默認(rèn)的GC,G1中有一些細(xì)節(jié)的概念在上文中沒(méi)有敘述,這里先介紹一下:
- Remembered Sets(Rsets):對(duì)于每個(gè)區(qū)域,都有一個(gè)集合記錄這個(gè)區(qū)域中所有的引用。
- G1 refinement:G1中需要有一系列的線程不斷地維護(hù)Rsets。
- Collection Sets(Csets):在垃圾回收中需要被回收的區(qū)域,這些區(qū)域中的可達(dá)對(duì)象(活著的對(duì)象)會(huì)被疏散。這些區(qū)域可能是任何年齡代。
- 寫屏障(Write Barriers):對(duì)于每一次賦值操作,G1都會(huì)有兩個(gè)寫屏障,寫之前(Pre-Write)一個(gè),寫之后(Post-Write)一個(gè)。Pre-write主要與SATB相關(guān),Post-write主要與Rsets相關(guān)
- Dirty Card Queue:寫屏障會(huì)將寫的記錄放入這個(gè)隊(duì)列,會(huì)有線程將這里的對(duì)象不斷的刷入Rsets。
- Green/Yellow/Red Zone:三個(gè)會(huì)影響處理Dirty Card Queue線程數(shù)的閾值。根據(jù)Dirty Card Queue中元素的個(gè)數(shù),可以來(lái)設(shè)置一些GC行為(可以認(rèn)為是邏輯上將Dirty Card Queue分隔成多個(gè)區(qū)域)。Green表示超過(guò)此閾值則開(kāi)始新建線程來(lái)處理這個(gè)隊(duì)列,Yellow表示超過(guò)此閾值,強(qiáng)制啟動(dòng)這些線程,Red表示超過(guò)此閾值則會(huì)讓寫操作的線程自己來(lái)執(zhí)行G1 refinement。
G1提供了豐富的基于不同目的的可調(diào)優(yōu)的參數(shù),列表如下:
參數(shù) | 描述 |
---|---|
-XX:+G1UseAdaptiveConcRefinement, | 調(diào)節(jié)G1 refinement所使用的資源。 |
-XX:G1ConcRefinementGreenZone=<ergo>, | 調(diào)節(jié)G1 refinement所使用的資源。 |
-XX:G1ConcRefinementYellowZone=<ergo>, | 調(diào)節(jié)G1 refinement所使用的資源。 |
-XX:G1ConcRefinementRedZone=<ergo>, | 調(diào)節(jié)G1 refinement所使用的資源。 |
-XX:G1ConcRefinementThreads=<ergo> | 調(diào)節(jié)G1 refinement所使用的資源。 |
-XX:G1RSetUpdatingPauseTimePercent=10 | 調(diào)節(jié)G1 refinement所需要的時(shí)間在整個(gè)垃圾回收時(shí)間的比例,G1會(huì)根據(jù)這個(gè)時(shí)間動(dòng)態(tài)地調(diào)節(jié)第一行的各個(gè)參數(shù)。 |
-XX:+ReduceInitialCardMarks | 批量執(zhí)行對(duì)象的生成,以減少初始標(biāo)記的時(shí)間 |
-XX:-ParallelRefProcEnabled | 使用多線程處理上文中所說(shuō)的在重新標(biāo)記階段對(duì)引用的處理 |
-XX:G1SummarizeRSetStatsPeriod=<n> | 設(shè)置n次垃圾回收后,打印Rsets的總結(jié)性報(bào)告。 |
-XX:GCTimeRatio=<n> | 設(shè)置GC吞吐量。GC總共應(yīng)該使用的時(shí)間是1 / (1 + n),這個(gè)參數(shù)會(huì)影響不同年齡代尺寸的增長(zhǎng)。 |
-XX:G1HeapRegionSize | 設(shè)置區(qū)域的大小 |
主要參考文檔: