GC原理解析
眾所周知,Java程式不用像C++程式在程式中自行處理記憶體的回收釋放。這是因為Java在JVM虛擬機器上增加了垃圾回收(GC)機制,用以在合適的時間觸發垃圾回收,將不需要的記憶體空間回收釋放,避免無限制的記憶體增長導致的OOM。作為一個合格的Java程式設計師,有必要了解Java GC相關知識。掌握GC知識一方面可以幫助我們快速排查因JVM導致的線上問題,另一方面也可以幫助我們在Java應用釋出之前合理地對JVM進行調優,提高應用的執行效率、可靠性和健壯性。
這篇文章將從以下幾點展開介紹:
- Java堆記憶體結構
- 分代回收演算法
- 垃圾收集器
- GC日誌
- JVM引數使用
本文所討論的虛擬機器為預設JDK所使用的HotSpot虛擬機器。
1. Java堆記憶體結構
為了後面章節知識點的理解,我們應該先對Java堆記憶體結構劃分有一定的瞭解。Java將堆記憶體分為3大部分:新生代、老年代和永久代,其中新生代又進一步劃分為Eden、S0、S1(Survivor)三個區。結構如下圖所示:
+---------------------------+-------------------------------+-------------------+ | | | | | | | Eden | S0 | S1 | Old generation | Perm | | | | | | | +---------------------------+-------------------------------+-------------------+ |<----Young Gen Space------>|
我們在程式中new出來的物件一般情況下都會在新生代裡的Eden區裡面分配空間,如果存活時間足夠長將會進入Survivor區,進而如果存活時間再長,還會被提升分配到老年代裡面。持久代裡面存放的是Class類元資料、方法描述等。
- S0和S1是兩個大小相等的區域,分配記憶體空間只會在其中某一個進行,另外一個空間是用來輔助進行新生代進行垃圾回收的,因為新生代的垃圾回收策略基於複製演算法,其思想是將Eden區及兩個Survivor中的某個區,如S0區裡面需要存活的物件複製到另外一個空的Survivor區,如S1區,然後就可以回收Eden和S0區域裡面的死亡物件。下一次回收就對調S0和S1兩個區的角色,S1用來存放存活物件而S0用來輔助回收垃圾,如此迴圈利用。
- 有些文章並不將永久代納入Java堆記憶體。其實永久代就是我們所說的方法區,而方法區經常被稱為Non-Heap(非堆)。僅僅在HotSpot虛擬機器的實現中才將GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區,對於其他的虛擬機器是不存在永久代這個概念的。
- 並非所有的物件建立都會在Eden區中分配記憶體空間。對於Serial和ParNew垃圾收集器,通過指定-XX:PretenureSizeThreshold={size}來設定超過這個閾值大小的物件直接進入老年代。
2. 分代回收演算法
我們一般討論的垃圾回收主要針對Java堆記憶體中的新生代和老年代,也正因為新生代和老年代結構上的不同,所以產生了分代回收演算法,即新生代的垃圾回收和老年代的垃圾回收採用的是不同的回收演算法。針對新生代,主要採用複製演算法,而針對老年代,通常採用標記-清除演算法或者標記-整理演算法來進行回收。
2.1 複製演算法
複製演算法的思想是將記憶體分成大小相等的兩塊區域,每次使用其中的一塊。當這一塊的記憶體用完了,就將還存活的物件複製到另一塊區域上,然後對該塊進行記憶體回收。示例圖如下所示:
這個演算法實現簡單,並且也相對高效,但是代價就是需要將犧牲一半的記憶體空間用於進行復制。有研究表明,新生代中的物件98%存活期很短,所以並不需要以1:1的比例來劃分整個新生代,通常的做法是將新生代記憶體空間劃分成一塊較大的Eden區和兩塊較小的Survivor區,兩塊Survivor區域的大小保持一致。每次使用Eden和其中一塊Survivor區,當回收的時候,將還存活的物件複製到另外一塊Survivor空間上,最後清除Eden區和一開始使用的Survivor區。假如用於複製的Survivor區放不下存活的物件,那麼會將物件存到老年代。
HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1:1,也就是說新生代中犧牲掉10%的空間而不是一半的空間。
2.2 標記-清除演算法
標記-清除(Mark-Sweep)演算法分為兩個階段:
- 標記
- 清除
在標記階段將標記出需要回收的物件空間,然後在下一個階段清除階段裡面,將這些標記出來的物件空間回收掉。這種演算法有兩個主要問題:一個是標記和清除的效率不高,另一個問題是在清理之後會產生大量不連續的記憶體碎片,這樣會導致在分配大物件時候無法找到足夠的連續記憶體而觸發另一次垃圾收集動作。標記-清除演算法示例圖如下所示:
2.3 標記-整理演算法
標記-整理(Mark-Compact)演算法有效預防了標記-清除演算法中可能產生過多記憶體碎片的問題。在標記需要回收的物件以後,它會將所有存活的物件空間挪到一起,然後再執行清理。示例圖如下所示:
標記-整理通常會在標記-清除演算法裡面作為備選方案,為了防止標記-清除後產生大量記憶體碎片而無法為大物件分配足夠記憶體的情況,如後面所講的Serial Old收集器(基於標記-整理演算法實現)將會作為CMS收集器(基於標記-清除演算法實現)的備選方案。
3. 垃圾收集器
因為新生代和老年代採用回收演算法的不同,垃圾收集器相應地也分為新生代收集器和老年代收集器。其中新生代收集器主要有Serial收集器、ParNew收集器和Parallel Scavenge收集器。老年代收集器主要有Serial Old收集器、Parallel Old收集器和CMS收集器。當然還包括了一款全新的、新生代老年代通用的G1收集器。各款收集器的搭配使用如下圖所示,其中有連線的代表收集器可以搭配使用,沒有連線的收集器表示不能搭配使用。
其中顯示為?號的收集器叫做G1收集器,是目前最新的收集器。這篇文章不會涉及到它。
3.1 新生代收集器
3.1.1 Serial收集器
Serial收集器作用於新生代,是一個單執行緒收集器,基於複製演算法實現。在進行垃圾回收的時候僅使用單條執行緒並且在回收的過程中會掛起所有的使用者執行緒(Stop The World)。Serial收集器是JVM client模式下預設的新生代收集器。
特別注意,Stop-The-World會掛起應用執行緒,造成應用的停頓。
3.1.2 ParNew收集器
ParNew收集器作用於新生代,是一個多執行緒收集器,基於複製演算法實現。相對於Serial收集器而言,在垃圾回收的時候會同時使用多條執行緒進行回收,但是它跟Serial收集器一樣,在回收過程中也是會掛起所有的使用者執行緒,從而造成應用的停頓。
3.1.3 Parallel Scavenge收集器
Parallel Scavenge收集器同樣作用於新生代,並且也是採用多執行緒和複製演算法來進行垃圾回收。Parallel Scavenge收集器關注的是吞吐量,即使得應用能夠充分使用CPU。它與ParNew收集器一樣,在回收過程會掛起所有的使用者執行緒,造成應用停頓。
所謂吞吐量就是CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值。即吞吐量 = 執行使用者程式碼時間 / (執行使用者程式碼時間 + 垃圾收集時間)。如果虛擬機器運行了100分鐘,其中垃圾收集花了1分鐘,那麼吞吐量就是99%。
3.2 老年代收集器
3.2.1 Serial Old收集器
Serial Old收集器作用於老年代,採用單執行緒和標記-整理演算法來實現垃圾回收。在回收垃圾的時候同樣會掛起所有使用者執行緒,造成應用的停頓。一般來說,老年代的容量都比新生代要大,所以當發生老年代的垃圾回收時,STW經歷的時間會比新生代所用的時間長得多。該收集器是JVM client模式下預設的老年代收集器。
Serial Old收集器還有一個重要的用途是作為CMS收集器的後備方案,在併發收集發生Concurrent Mode Failure的時候使用,進行記憶體碎片的整理。
3.2.2 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,採用多執行緒和標記-整理演算法來實現老年代的垃圾回收。這個收集器主要是為了配合Parallel Scavenge收集器的使用,即當新生代選擇了Parallel Scavenge收集器的情況下,老年代可以選擇Parallel Old收集器。
在JDK1.6以前並沒有提供Parallel Scavenge收集器,所以在1.6版本以前,Parallel Scavenge收集器只能與Serial Old收集器搭配使用。
3.2.3 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一款真正實現了併發收集的老年代收集器。CMS收集器以獲取最短回收停頓時間為目標,採用多執行緒併發以及標記-清除演算法來實現垃圾回收。CMS只在初始化標記和重新標記階段需要掛起使用者執行緒,造成一定的應用停頓(STW),而其他階段收集執行緒都可以與使用者執行緒併發交替進行,不必掛起使用者執行緒,所以並不會造成應用的停頓。CMS收集器可以最大程度地減少因垃圾回收而造成應用停頓的時間。
CMS垃圾收集分為以下幾個階段:
(1) 初始化標記 (inital mark)
這個階段僅僅是標記了GC Roots能夠直接關聯到的物件,速度很快,所以基本上感受不到STW帶來的停頓。
(2) 併發標記 (concurrent mark)
併發標記階段完成的任務是從第一階段收集到的物件引用開始,遍歷所有其他的物件引用,並標記所有需要回收的物件。這個階段,收集執行緒與使用者執行緒併發交替執行,不必掛起使用者執行緒,所以並不會造成應用停頓。
(3) 併發預清除 (concurrent-pre-clean)
併發預清除階段是為了下一個階段做準備,為的是儘量減少應用停頓的時間。
(4) 重新標記 (remark)
這個階段將會修正併發標記期間因為使用者程式繼續運作而導致標記產生變動的那部分物件的標記記錄(有可能物件重新被引用或者新物件可以被回收)。這個階段的停頓時間比初始標記階段要長一些,但是遠比並發標記的時間短。
(5) 併發清除 (concurrent sweep)
這個階段將真正執行垃圾回收,將那些不被使用的物件記憶體回收掉。
(6) 併發重置 (concurrent reset)
收集器做一些收尾的工作,以便下一次GC週期能有一個乾淨的狀態。
使用CMS要注意以下兩個關鍵詞:
- concurrent mode failure
- promotion failed
對於採用CMS進行老年代GC的程式而言,尤其要注意GC日誌中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。promotion failed是在進行Minor GC時,新生代的survivor區放不下,物件只能放入老年代,而此時老年代也放不下造成的。concurrent mode failure是在執行CMS GC的過程中同時有物件要放入老年代(滿足一定年齡的物件或者大物件),而此時老年代空間不足造成的。
- 通常我們把發生在新生代的垃圾回收稱為Minor GC,而把發生在老年代的垃圾回收稱為Major GC,而FullGC是指整個堆記憶體的垃圾回收,包括對新生代、老年代和持久代的回收。一般情況下應用程式發生Minor GC的次數要遠遠大於Major GC和Full GC的次數。
- 在講解GC的時候會涉及到並行和併發兩個概念。在這裡,並行指的是多個GC收集執行緒之間並行進行垃圾回收。而併發指的是多個GC收集執行緒與所有的使用者執行緒能夠交替執行。
4. GC日誌
瞭解GC日誌可以幫助我們更好地排查一些線上問題,如OOM、應用停頓時間過長等等。GC日誌對我們進行JVM調優也是很有幫助的。採用不同的GC收集器所產生的GC日誌的格式會稍微不同,但虛擬機器設計者為了方便使用者閱讀,將各個收集器的日誌都維持一定的共性。
具有一定共性的的GC日誌格式大致如下所示:
<datestamp>:[GC[<collector>:<start occupancy1>-><end occupancy1>(total size1),<pause time1> secs]<start occupancy2>-><end occupancy2>(total size2),<pause time2> secs] [Times:<user time> <system time>, <real time>]
- 1
-
datestamp : 表示GC日誌產生的時間點,如果指定的jvm引數是
-XX:+PrintGCTimeStamps
,那麼輸出的是相對於虛擬機器啟動時間的時間戳,如果指定的是-XX:+PrintGCDateStamps
,那麼輸出的是具體的時間格式,可讀性更高 -
GC : 表示發生GC的型別,有GC(代表MinorGC)和FullGC兩種情況
-
collector : 表示GC收集器型別,取值可能是DefNew、ParNew、PSYoungGen、Tenured、ParOldGen、PSPermGen等等
-
start occupancy1 : 表示發生回收之前佔用的記憶體空間
-
end occupancy1 : 表示發生回收以後還佔用的記憶體空間
-
total size1 : 該堆區域所擁有的總記憶體空間
-
pause time1 : 發生垃圾收集的時間
-
start occupancy2 : 表示回收前Java堆記憶體總佔用空間
-
end occupancy2 : 表示回收後Java堆記憶體還佔用的總空間
-
total size2 : 表示Java堆記憶體總空間
-
pause time2 : 表示整個堆回收消耗時間
-
[Times:\
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/myapp-gc.log
- 1
- -XX:+PrintGCDetails : 表示輸出GC的詳細情況
- -XX:+PrintGCDateStamps : 指定輸出GC時的時間格式,比-XX:+PrintGCTimeStamps可讀性更高
- -Xloggc : 指定gc日誌的存放位置
下面我們將具體解讀幾種GC收集器組合下產生的GC日誌。
輔助講解的例子程式:
package com.test.gc;
import java.util.ArrayList;
import java.util.List;
public class TestGC {
private static class BigObject {
byte[] bigBytes = new byte[1024 * 1024];
}
public static void main(String[] args) {
keepInMemory();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();
}
private static void keepInMemory() {
List<BigObject> memList = new ArrayList<BigObject>();
for(int i = 0; i < 55;i ++) {
memList.add(new BigObject());
}
}
}
(1) Serial + Serial Old
-XX:+UseSerialGC
-Xms64m -Xmx64m
-Xmn32m
-XX:SurvivorRatio=8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
[GC log]
2016-04-22T15:00:00.647+0800: [GC2016-04-22T15:00:00.648+0800: [DefNew: 26152K->2513K(29504K), 0.0131820 secs] 26152K->25042K(62272K), 0.0132320 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2016-04-22T15:00:00.663+0800: [GC2016-04-22T15:00:00.663+0800: [DefNew: 28155K->28155K(29504K), 0.0000125 secs]2016-04-22T15:00:00.663+0800: [Tenured: 22528K->31745K(32768K), 0.0114797 secs] 50684K->49617K(62272K), [Perm : 2753K->2753K(21248K)], 0.0115361 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2016-04-22T15:00:01.676+0800: [Full GC2016-04-22T15:00:01.676+0800: [Tenured: 31745K->463K(32768K), 0.0039332 secs] 56964K->463K(62272K), [Perm : 2753K->2753K(21248K)], 0.0039788 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
以GC日誌第一行為例子,發生的是Minor GC。最開始的2016-04-22T15:00:00.647+0800
表示發生GC的時間;DefNew
表示新生代採用的是Serial收集器。26152K->2513K(29504K), 0.0131820 secs
表示回收前新生代記憶體佔用是26152K(約26M),回收後記憶體佔用是2513K(約2.5M),括號裡面的數字表示新生代的可用總容量為29504K(約29M),一般來說這個數字約等於Eden區加上一個Survivor區的容量大小。最後的0.0131820 secs
代表Minor GC消耗的時間。
從GC日誌第二行可以看出,新生代已經容納不下新生的物件,需要把存活的物件不斷的挪到老年,Tenured
表示老年代採用的是Serial Old收集器。[Tenured: 22528K->31745K(32768K), 0.0114797 secs]
表明老年代的容量不斷被佔用,並且已經接近滿了,如果這個時候還不斷有新物件生成並存活,那麼將會發生OOM異常。
最後發生的FullGC是由於程式呼叫了System.gc()
造成的,這個時候keepInMemory()
函式退出,區域性變數將可以被回收,所以呼叫gc方法可以儘快通知虛擬機器進行垃圾回收,所以我們可以看到堆記憶體的佔用情況很快地降了下來。
特別注意,如果把System.gc()方法寫在keepInMemory()方法的最後,那麼堆記憶體將不會被回收,因為方法沒有退出,區域性引用物件將一直存在於記憶體。
(2) Parallel Scavenge + Parallel Old
-XX:+UseParallelGC -XX:+UseParallelOldGC
-Xms64m -Xmx64m
-Xmn32m
-XX:SurvivorRatio=8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
[GC log]
2016-04-22T14:09:28.990+0800: [GC [PSYoungGen: 26174K->2688K(29696K)] 26174K->25216K(62464K), 0.0090217 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2016-04-22T14:09:28.999+0800: [Full GC [PSYoungGen: 2688K->0K(29696K)] [ParOldGen: 22528K->25040K(32768K)] 25216K->25040K(62464K) [PSPermGen: 2748K->2747K(21504K)], 0.0084858 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2016-04-22T14:09:29.011+0800: [Full GC [PSYoungGen: 25657K->17408K(29696K)] [ParOldGen: 25040K->32208K(32768K)] 50698K->49616K(62464K) [PSPermGen: 2750K->2750K(21504K)], 0.0100313 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
2016-04-22T14:09:29.022+0800: [Full GC [PSYoungGen: 24758K->0K(29696K)] [ParOldGen: 32208K->462K(32768K)] 56966K->462K(62464K) [PSPermGen: 2750K->2750K(21504K)], 0.0057976 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
以日誌第一行為例,發生的是Minor GC。最開始的2016-04-22T14:09:28.990+0800
表示發生GC的時間;PSYoungGen
表示新生代採用的是Parallel Scavenge收集器,後面的26174K->2688K(29696K)
表示新生代回收前使用了26174K(約26M)的空間而回收後還佔用了2688K(約2.6M)的空間,括號裡面的數值表示新生代的可用總容量為29696K(約29M),而後面的0.0090217 secs
表示此次Minor GC耗費的時間。
以日誌第二行為例,發生的是Full GC。其中包括了新生代、老年代和持久代的垃圾回收。從資料可以看出,在老年代發生GC以後佔有的記憶體比回收前還要多[ParOldGen: 22528K->25040K(32768K)]
,證明此時程式應該有大量的物件存活並且由於新生代已經存放不下去了,只能由老年代來存放。持久代的垃圾回收記憶體佔用基本保持不變。
而在日誌最後一行發生的Full GC是因為程式在結束的時候呼叫了System.gc()
。所以堆記憶體的佔用情況降了下來。
(3) ParNew + CMS
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
-Xms64m -Xmx64m
-Xmn32m
-XX:SurvivorRatio=8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
[GC log]
2016-04-22T14:34:06.134+0800: [GC2016-04-22T14:34:06.134+0800: [ParNew: 26152K->2532K(29504K), 0.0076385 secs] 26152K->25069K(62272K), 0.0076904 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2016-04-22T14:34:06.144+0800: [GC2016-04-22T14:34:06.145+0800: [ParNew: 28174K->28174K(29504K), 0.0000311 secs]2016-04-22T14:34:06.145+0800: [CMS: 22536K->31745K(32768K), 0.0170668 secs] 50711K->49617K(62272K), [CMS Perm : 2754K->2753K(21248K)], 0.0171470 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
2016-04-22T14:34:06.162+0800: [GC [1 CMS-initial-mark: 31745K(32768K)] 50641K(62272K), 0.0002688 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.164+0800: [CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.165+0800: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.165+0800: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.165+0800: [GC[YG occupancy: 25219 K (29504 K)]2016-04-22T14:34:06.165+0800: [Rescan (parallel) , 0.0002540 secs]2016-04-22T14:34:06.165+0800: [weak refs processing, 0.0000090 secs]2016-04-22T14:34:06.165+0800: [scrub string table, 0.0001010 secs] [1 CMS-remark: 31745K(32768K)] 56965K(62272K), 0.0004079 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.165+0800: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:06.166+0800: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-04-22T14:34:07.163+0800: [Full GC2016-04-22T14:34:07.163+0800: [CMS: 31745K->465K(32768K), 0.0066109 secs] 56965K->465K(62272K), [CMS Perm : 2753K->2753K(21248K)], 0.0066795 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
以GC日誌第一行為例,ParNew
表示使用的新生代收集器為Parallel收集器。從第二行可以看出這個時候新生代已經容納不下新物件了,物件往老年代放,造成老年代的容量不斷增加。[CMS: 22536K->31745K(32768K), 0.0170668 secs] 50711K->49617K(62272K),
表示老年代使用的是CMS收集器。並且回收後的容量比回收前的容量還要高。
注意這裡的回收是由新生代引起的
而後顯示的是老年代CMS收集器發生GC的日誌,從日誌中可以看出CMS收集的所有階段。
5. JVM引數使用
5.1 堆記憶體相關
- -Xms 與 -Xmx
-Xms
用於指定Java應用使用的最小堆記憶體,如-Xms1024m
表示將Java應用最小堆設定為1024M。-Xmx
用於指定Java應用使用的最大堆記憶體,如-Xmx1024m
表示將Java應用最大堆設定為1024m。過小的堆記憶體可能會造成程式丟擲OOM異常,所以正常釋出的應用應該明確指定這兩個引數。並且,一般會選擇將-Xms
與-Xmx
設定成一樣大小,防止JVM動態調整堆記憶體容量對程式造成效能影響。
- -Xmn
通過-Xmn
可以設定堆記憶體中新生代的容量,以此達到間接控制老年代容量的作用,因為沒有JVM引數可以直接控制老年代的容量。如-Xmn256m
表示將新生代容量設定為256M。假如這個時候額外指定了-Xms1024m -Xmx1024m
,那麼老年代的容量為768M(1024-256=768)。
- -XX:PermSize 與 -XX:MaxPermSize
-XX:PermSize
和-XX:MaxPermSize
分別用於設定永久代的容量和最大容量。如-XX:PermSize=64m -XX:MaxPermSize=128m
表示將永久代的初始容量設定為64M,最大容量設定為128M。
- -XX:SurvivorRatio
這個引數用於設定新生代中Eden區和Survivor(S0、S1)的容量比值。預設設定為-XX:SurvivorRatio=8
表示Eden區與Survivor的容量比例為8:1:1。假設-Xmn256m -XX:Survivor=8
,那麼Eden區容量為204.8M(256M / 10 * 8),S0和S1區的容量大小均為25.6M(256M / 10 * 1)。
5.2 GC收集器相關
- -XX:+UseSerialGC
虛擬機器執行在client模式下的預設值,使用這個引數表示虛擬機器將使用Serial + Serial Old收集器組合進行垃圾回收。
-XX:+UseSerialGC表示使用這個設定,而-XX:-UseSerialGC表示禁用這個設定。
- -XX:+UseParNewGC
使用這個設定以後,虛擬機器將使用ParNew + Serial Old收集器組合進行垃圾回收。
- -XX:+UseConcMarkSweepGC
使用這個設定以後,虛擬機器將使用ParNew + CMS + Serial Old的收集器組合進行垃圾回收。注意Serial Old收集器將作為CMS收集器出現Concurrent Mode Failure失敗後的後備收集器來進行回收(將會整理記憶體碎片)。
- -XX:+UseParallelGC
虛擬機器執行在server模式下的預設值。使用這個設定,虛擬機器將使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行垃圾回收。
- -XX:+UseParallelOldGC
使用這個設定以後,虛擬機器將使用Parallel Scavengen + Parallel Old的收集器組合進行垃圾回收。
- -XX:PretenureSizeThreshold
設定直接晉升到老年代的物件大小,大於這個引數的物件將直接在老年代分配,而不是在新生代分配。注意這個值只能設定為位元組,如-XX:PretenureSizeThreshold=3145728
表示超過3M的物件將直接在老年代分配。
- -XX:MaxTenuringThreshold
設定晉升到老年代的物件年齡。每個物件在堅持過一次Minor GC之後,年齡就會加1,當超過這個值時就進入老年代。預設設定為-XX:MaxTenuringThreshold=15
。
- -XX:ParellelGCThreads
設定並行GC時進行記憶體回收的執行緒數。只有當採用的垃圾回收器是採用多執行緒模式,包括ParNew、Parallel Scavenge、Parallel Old、CMS,這個引數的設定才會有效。
- -XX:CMSInitiatingOccupancyFraction
設定CMS收集器在老年代空間被使用多少(百分比)後觸發垃圾收集。預設設定-XX:CMSInitiatingOccupancyFraction=68
表示老年代空間使用比例達到68%時觸發CMS垃圾收集。僅當老年代收集器設定為CMS時候這個引數才有效。
- -XX:+UseCMSCompactAtFullCollection
設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片整理。僅當老年代收集器設定為CMS時候這個引數才有效。
- -XX:CMSFullGCsBeforeCompaction
設定CMS收集器在進行多少次垃圾收集後再進行一次記憶體碎片整理。如設定-XX:CMSFullGCsBeforeCompaction=2
表示CMS收集器進行了2次垃圾收集之後,進行一次記憶體碎片整理。僅當老年代收集器設定為CMS時候這個引數才有效。
5.3 GC日誌相關
- -XX:+PrintGCDetails
表示輸出GC的詳細情況。
- -XX:+PrintGCDateStamps
指定輸出GC時的時間格式,比指定-XX:+PrintGCTimeStamps
可讀性更高。
- -Xloggc
指定gc日誌的存放位置。如-Xloggc:/var/log/myapp-gc.log
表示將gc日誌儲存在磁碟/var/log/
目錄,檔名為myapp-gc.log
。