Serial收集器
介紹
? Serial收集器是最基礎(chǔ)灾前、歷史最悠久的收集器,曾經(jīng)(在JDK 1.3.1之前)是HotSpot虛擬機(jī)新生代收集器的唯一選擇哥倔。大家只看名字就能夠猜到没陡,這個(gè)收集器是一個(gè)單線程工作的收集器,但它的“單線程”的意義并不僅僅是說(shuō)明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作埋虹,更重要的是強(qiáng)調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程娩怎,直到它收集結(jié)束搔课。
? 事實(shí)上,迄今為止截亦,它依然是HotSpot虛擬機(jī)運(yùn)行在客戶端模式下的默認(rèn)新生代收集器爬泥,有著優(yōu)于其他收集器的地方旦事,那就是簡(jiǎn)單而高效(與其他收集器的單線程相比),對(duì)于內(nèi)存資源受限的環(huán)境急灭,它是所有收集器里額外內(nèi)存消耗(Memory Footprint)最小的姐浮;對(duì)于單核處理器或處理器核心數(shù)較少的環(huán)境來(lái)說(shuō),Serial收集器由于沒有線程交互的開銷葬馋,專心做垃圾收集自然可以獲得最高的單線程收集效率卖鲤。在用戶桌面的應(yīng)用場(chǎng)景以及近年來(lái)流行的部分微服務(wù)應(yīng)用中,分配給虛擬機(jī)管理的內(nèi)存一般來(lái)說(shuō)并不會(huì)特別大畴嘶,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的內(nèi)存蛋逾,桌面應(yīng)用甚少超過(guò)這個(gè)容量),垃圾收集的停頓時(shí)間完全可以控制在十幾窗悯、幾十毫秒区匣,最多一百多毫秒以內(nèi),只要不是頻繁發(fā)生收集蒋院,這點(diǎn)停頓時(shí)間對(duì)許多用戶來(lái)說(shuō)是完全可以接受的亏钩。所以,Serial收集器對(duì)于運(yùn)行在客戶端模式下的虛擬機(jī)來(lái)說(shuō)是一個(gè)很好的選擇欺旧。
記憶
serial收集器
- 介紹
- 一個(gè)單線程的收集器姑丑,在進(jìn)行垃圾收集時(shí)候,必須暫停其他所有的工作線程直到它收集結(jié)束辞友。
- 在新生代使用采取復(fù)制算法栅哀。
- 優(yōu)點(diǎn):簡(jiǎn)單而高效
- <font color=green>對(duì)于內(nèi)存資源受限的環(huán)境</font>,它是所有收集器里<font color=apple green>額外內(nèi)存消耗(Memory Footprint)最小的</font>称龙。
- <font color=green>對(duì)于單核處理器或處理器核心數(shù)較少的環(huán)境</font>來(lái)說(shuō)留拾,Serial收集器由于<font color=red>沒有線程交互的開銷</font>,<font color=apple green>專心做垃圾收集自然可以獲得最高的單線程收集效率</font>鲫尊。
- 例子
- 在用戶桌面的應(yīng)用場(chǎng)景以及近年來(lái)流行的部分微服務(wù)應(yīng)用中痴柔,分配給虛擬機(jī)管理的內(nèi)存一般來(lái)說(shuō)并不會(huì)特別大,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的內(nèi)存马昨,桌面應(yīng)用甚少超過(guò)這個(gè)容量)竞帽,垃圾收集的停頓時(shí)間完全可以控制在十幾、幾十毫秒鸿捧,最多一百多毫秒以內(nèi),只要不是頻繁發(fā)生收集疙渣,這點(diǎn)停頓時(shí)間對(duì)許多用戶來(lái)說(shuō)是完全可以接受的匙奴。
- 例子
- 缺點(diǎn)
- STW,這項(xiàng)工作是由虛擬機(jī)在后臺(tái)自動(dòng)發(fā)起和自動(dòng)完成的妄荔,在用戶不可知泼菌、不可控的情況下把用戶的正常工作的線程全部停掉谍肤,這對(duì)很多應(yīng)用來(lái)說(shuō)都是不能接受的。
- 補(bǔ)充
- 從JDK 1.3開始哗伯,一直到現(xiàn)在最新的JDK 13荒揣,HotSpot虛擬機(jī)開發(fā)團(tuán)隊(duì)為消除或者降低用戶線程因垃圾收集而導(dǎo)致停頓的努力一直持續(xù)進(jìn)行著,從Serial收集器到Parallel收集器焊刹,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器系任,最終至現(xiàn)在垃圾收集器的最前沿成果Shenandoah和ZGC等,我們看到了一個(gè)個(gè)越來(lái)越構(gòu)思精巧虐块,越來(lái)越優(yōu)秀俩滥,也越來(lái)越復(fù)雜的垃圾收集器不斷涌現(xiàn),用戶線程的停頓時(shí)間在持續(xù)縮短贺奠,但是仍然沒有辦法徹底消除(這里不去討論RTSJ中的收集器)霜旧,探索更優(yōu)秀垃圾收集器的工作仍在繼續(xù)。
- 補(bǔ)充
- STW,這項(xiàng)工作是由虛擬機(jī)在后臺(tái)自動(dòng)發(fā)起和自動(dòng)完成的妄荔,在用戶不可知泼菌、不可控的情況下把用戶的正常工作的線程全部停掉谍肤,這對(duì)很多應(yīng)用來(lái)說(shuō)都是不能接受的。
- 對(duì)應(yīng)jvm參數(shù)
- -XX:+UseSerialGC
- 開啟后的收集器的算法組合
- <font color=red>Serial(Young區(qū)用) + Serial Old(Old區(qū)用)的收集器組合</font>儡率,新生代挂据、老年代都會(huì)使用串行回收收集器,<font color=red>新生代使用復(fù)制算法儿普,老年代使用標(biāo)記-整理算法</font>棱貌。
Serial/Serial Old收集器的運(yùn)行過(guò)程
例子
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Random rand = new Random(System.nanoTime());
try {
String str = "Hello, World";
while(true) {
str += str + rand.nextInt(Integer.MAX_VALUE) + rand.nextInt(Integer.MAX_VALUE);
}
}catch (Throwable e) {
e.printStackTrace();
}
}
}
jvm參數(shù)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
輸出
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation
-XX:+UseSerialGC /注意這里 DefNew Default New Generation/
[GC (Allocation Failure) [DefNew: 2346K->320K(3072K), 0.0012956 secs] 2346K->1030K(9920K), 0.0013536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2888K->0K(3072K), 0.0013692 secs] 3598K->2539K(9920K), 0.0014059 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2065K->0K(3072K), 0.0011613 secs] 4604K->4550K(9920K), 0.0011946 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2056K->0K(3072K), 0.0010394 secs] 6606K->6562K(9920K), 0.0010808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2011K->2011K(3072K), 0.0000124 secs][Tenured /注意這里old(老年代)是Tenured/: 6562K->2537K(6848K), 0.0021691 secs] 8574K->2537K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0024399 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2059K->2059K(3072K), 0.0000291 secs][Tenured: 6561K->6561K(6848K), 0.0012330 secs] 8620K->6561K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0012888 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 6561K->6547K(6848K), 0.0017784 secs] 6561K->6547K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0018111 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.lun.jvm.GCDemo.main(GCDemo.java:23)
Heap
def new generation total 3072K, used 105K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61a7c8, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 6547K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 95% used [0x00000000ff950000, 0x00000000fffb4c30, 0x00000000fffb4e00, 0x0000000100000000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
日志解讀:
1.使用SerialGC時(shí),年輕代是表示為:DefNew(DefNew Default New Generation)箕肃,老年代表示為:Tenured婚脱;
2.Allocation Failure:
表明本次引起GC的原因是因?yàn)樵谀贻p代中沒有足夠的空間能夠存儲(chǔ)新的數(shù)據(jù)了。
3.日志對(duì)照表
ParNew收集器
介紹
? ParNew收集器實(shí)質(zhì)上是Serial收集器的多線程并行版本勺像,除了同時(shí)使用多條線程進(jìn)行垃圾收集之外障贸,其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold吟宦、-XX:HandlePromotionFailure等)篮洁、收集算法、Stop The World殃姓、對(duì)象分配規(guī)則袁波、回收策略等都與Serial收集器完全一致,在實(shí)現(xiàn)上這兩種收集器也共用了相當(dāng)多的代碼蜗侈。
? ParNew收集器除了支持多線程并行收集之外篷牌,其他與Serial收集器相比并沒有太多創(chuàng)新之處,但它卻是不少運(yùn)行在服務(wù)端模式下的HotSpot虛擬機(jī)踏幻,尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集器枷颊,其中有一個(gè)與功能、性能無(wú)關(guān)但其實(shí)很重要的原因是:除了Serial收集器外,目前只有它能與CMS收集器配合工作夭苗。
在JDK 5發(fā)布時(shí)信卡,HotSpot推出了一款在強(qiáng)交互應(yīng)用中幾乎可稱為具有劃時(shí)代意義的垃圾收集器——CMS收集器。這款收集器是HotSpot虛擬機(jī)中第一款真正意義上支持并發(fā)的垃圾收集器题造,它首次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作傍菇。
記憶
ParNew
-
介紹
- Serial收集器的多線程并行版本,使用多線程進(jìn)行垃圾回收界赔,在垃圾收集時(shí)丢习,會(huì)Stop-The-World暫停其他所有的工作線程直到它收集結(jié)束。
- 在新生代使用復(fù)制算法仔蝌。
-
特點(diǎn):
-
除了Serial收集器外泛领,目前只有它能與CMS收集器配合工作
這是JDK 7之前運(yùn)行在服務(wù)端模式下的HotSpot虛擬機(jī)首選ParNew作為垃圾收集器的原因庶近。
CMS作為老年代的收集器亥啦,卻無(wú)法與JDK 1.4.0中已經(jīng)存在的新生代收集器Parallel Scavenge配合工作缰揪,所以在JDK 5中使用CMS來(lái)收集老年代的時(shí)候拆挥,新生代只能選擇ParNew或者Serial收集器中的一個(gè)家肯。
ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC選項(xiàng))的默認(rèn)新生代收集器屉凯,也可以使用-XX:+/-UseParNewGC選項(xiàng)來(lái)強(qiáng)制指定或者禁用它萎战。
-
ParNew可以說(shuō)是HotSpot虛擬機(jī)中第一款退出歷史舞臺(tái)的垃圾收集器
- 可以說(shuō)直到CMS的出現(xiàn)才鞏固了ParNew的地位媳否,但成也蕭何敗也蕭何特恬,隨著垃圾收集器技術(shù)的不斷改進(jìn)执俩,更先進(jìn)的G1收集器帶著CMS繼承者和替代者的光環(huán)登場(chǎng)。G1是一個(gè)面向全堆的收集器癌刽,不再需要其他新生代收集器的配合工作役首。
- 所以自JDK 9開始,ParNew加CMS收集器的組合就不再是官方推薦的服務(wù)端模式下的收集器解決方案了显拜。官方希望它能完全被G1所取代衡奥,甚至還取消了ParNew加Serial Old以及Serial加CMS這兩組收集器組合的支持(其實(shí)原本也很少人這樣使用),并直接取消了-XX:+UseParNewGC參數(shù)远荠,這意味著ParNew和CMS從此只能互相搭配使用矮固,再也沒有其他收集器能夠和它們配合了。
- 也可以這么理解:自JDK1.9之后譬淳,ParNew合并入CMS档址,成為它專門處理新生代的組成部分。
-
-
性能
- 單核情況下不會(huì)比Serial收集器有更好的效果
- ParNew收集器在單核心處理器的環(huán)境中絕對(duì)不會(huì)有比Serial收集器更好的效果邻梆,甚至由于存在線程交互的開銷守伸,該收集器在通過(guò)超線程(Hyper-Threading)技術(shù)實(shí)現(xiàn)的偽雙核處理器環(huán)境中都不能百分之百保證超越Serial收集器。
- ParNew更適合在多核情況下使用
- 多核情況下确虱,ParNew對(duì)于垃圾收集時(shí)系統(tǒng)資源的高效利用還是很有好處的含友。
- 它默認(rèn)開啟的收集線程數(shù)與處理器核心數(shù)量相同
- 在處理器核心非常多(譬如32個(gè)替裆,現(xiàn)在CPU都是多核加超線程設(shè)計(jì)校辩,服務(wù)器達(dá)到或超過(guò)32個(gè)邏輯核心的情況非常普遍)的環(huán)境中窘问,可以使用-XX:ParallelGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。
- 單核情況下不會(huì)比Serial收集器有更好的效果
-
開啟參數(shù)
- -XX:+UseParNewGC
- 開啟上述參數(shù)后宜咒,會(huì)使用:ParNew(Young區(qū))+ Serial Old的收集器組合惠赫,新生代使用復(fù)制算法,老年代采用標(biāo)記-整理算法
- 配合-XX:ParallelGCThreads參數(shù)故黑,限制線程數(shù)量儿咱,默認(rèn)開啟和CPU數(shù)目相同的線程數(shù)
- -XX:+UseParNewGC
ParNew運(yùn)行過(guò)程
例子
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Random rand = new Random(System.nanoTime());
try {
String str = "Hello, World";
while(true) {
str += str + rand.nextInt(Integer.MAX_VALUE) + rand.nextInt(Integer.MAX_VALUE);
}
}catch (Throwable e) {
e.printStackTrace();
}
}
}
jvm參數(shù)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
輸出
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
[GC (Allocation Failure) [ParNew: 2702K->320K(3072K), 0.0007029 secs] 2702K->1272K(9920K), 0.0007396 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2292K->37K(3072K), 0.0010829 secs] 3244K->2774K(9920K), 0.0011000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2005K->9K(3072K), 0.0008401 secs] 4742K->5624K(9920K), 0.0008605 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1974K->1974K(3072K), 0.0000136 secs][Tenured: 5615K->3404K(6848K), 0.0021646 secs] 7589K->3404K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0022520 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1918K->2K(3072K), 0.0008094 secs] 5322K->5324K(9920K), 0.0008273 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1970K->1970K(3072K), 0.0000282 secs][Tenured: 5322K->4363K(6848K), 0.0018652 secs] 7292K->4363K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0019205 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 4363K->4348K(6848K), 0.0023131 secs] 4363K->4348K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0023358 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.lun.jvm.GCDemo.main(GCDemo.java:22)
Heap
par new generation total 3072K, used 106K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61a938, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 4348K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 63% used [0x00000000ff950000, 0x00000000ffd8f3a0, 0x00000000ffd8f400, 0x0000000100000000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
日志解讀:
1.使用ParNew時(shí),年輕代是表示為ParNew(Parallel New Generation)老年代是表示為Tenured场晶。
Parallel Scavenge 收集器
記憶
Parallel Scavenge
- 介紹
- Parallel Scavenge收集器也是一款新生代收集器混埠,它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器,也是能夠并行收集的多線程收集器诗轻,Parallel Scavenge的諸多特性從表面上看和ParNew非常相似钳宪。
- 在新生代使用復(fù)制算法。
- 吞吐量有限收集器:該收集器的更注重的是達(dá)到一個(gè)<font color=red>可控的吞吐量</font>扳炬。
-
吞吐量:處理器用于運(yùn)行用戶代碼的時(shí)間與處理器總消耗時(shí)間的比值吏颖。
image.png - 解釋
- 如果虛擬機(jī)完成某個(gè)任務(wù),用戶代碼加上垃圾收集總共耗費(fèi)了100分鐘恨樟,其中垃圾收集花掉1分鐘半醉,那吞吐量就是99%。
- 停頓時(shí)間越短就越適合<font color=apple green>需要與用戶交互</font>或需要<font color=apple green>保證服務(wù)響應(yīng)質(zhì)量</font>的程序劝术,良好的響應(yīng)速度能提升用戶體驗(yàn)缩多;
- 而高吞吐量則可以最高效率地利用處理器資源,盡快完成程序的運(yùn)算任務(wù)养晋,主要適合在后臺(tái)運(yùn)算而不需要太多交互的分析任務(wù)衬吆。
-
- jvm參數(shù)
- 激活參數(shù)
- -XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)
- 配合使用
- -XX:MaxGCPauseMillis:控制最大垃圾收集停頓時(shí)間
- 允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡力保證內(nèi)存回收花費(fèi)的時(shí)間不超過(guò)用戶設(shè)定值匙握。
- 不過(guò)大家不要異想天開地認(rèn)為如果把這個(gè)參數(shù)的值設(shè)置得更小一點(diǎn)就能使得系統(tǒng)的垃圾收集速度變得更快咆槽,<font color=apple green>垃圾收集停頓時(shí)間縮短</font>是以<font color=red>犧牲</font><font color=green>吞吐量</font>和<font color=green>新生代空間</font>為代價(jià)換取的。
- 系統(tǒng)把新生代調(diào)得小一些圈纺,收集300MB新生代肯定比收集500MB快秦忿,但這也直接導(dǎo)致垃圾收集發(fā)生得更頻繁,原來(lái)10秒收集一次蛾娶、每次停頓100毫秒灯谣,現(xiàn)在變成5秒收集一次、每次停頓70毫秒蛔琅。停頓時(shí)間的確在下降胎许,但吞吐量也降下來(lái)了(<font color=red>吞吐量越低,用戶代碼執(zhí)行時(shí)間越短</font>)。
- -XX:GCTimeRatio:直接設(shè)置吞吐量大小
- 參數(shù)的值則應(yīng)當(dāng)是一個(gè)大于0小于100的整數(shù)辜窑,也就是<font color=red>垃圾收集時(shí)間占總時(shí)間的比率</font>钩述,相當(dāng)于<font color=red>吞吐量的倒數(shù)</font>。
- 譬如把此參數(shù)設(shè)置為19穆碎,那允許的最大垃圾收集時(shí)間就占總時(shí)間的5%(即1/(1+19))牙勘,默認(rèn)值為99,即允許最大1%(即1/(1+99))的垃圾收集時(shí)間所禀。
- -XX:+UseAdaptiveSizePolicy
- 自適應(yīng)的調(diào)節(jié)策略
- 這是一個(gè)開關(guān)參數(shù)方面,當(dāng)這個(gè)參數(shù)被激活之后,就不需要人工指定新生代的大猩恰(-Xmn)恭金、Eden與Survivor區(qū)的比例(-XX:SurvivorRatio)、晉升老年代對(duì)象大泄硬摺(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了横腿,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量辙培。
- 如果操作者對(duì)于收集器運(yùn)作不太了解蔑水,手工優(yōu)化存在困難的話,使用Parallel Scavenge收集器配合自適應(yīng)調(diào)節(jié)策略扬蕊,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成也許是一個(gè)很不錯(cuò)的選擇搀别。
- 只需要把基本的內(nèi)存數(shù)據(jù)設(shè)置好(如-Xmx設(shè)置最大堆),然后使用-XX:MaxGCPauseMillis參數(shù)(更關(guān)注最大停頓時(shí)間)或-XX:GCTimeRatio(更關(guān)注吞吐量)參數(shù)給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo)尾抑,那具體細(xì)節(jié)參數(shù)的調(diào)節(jié)工作就由虛擬機(jī)完成了歇父。
- 自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器區(qū)別于ParNew收集器的一個(gè)重要特性。
- 自適應(yīng)的調(diào)節(jié)策略
- -XX:MaxGCPauseMillis:控制最大垃圾收集停頓時(shí)間
- 激活參數(shù)
例子
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Random rand = new Random(System.nanoTime());
try {
String str = "Hello, World";
while(true) {
str += str + rand.nextInt(Integer.MAX_VALUE) + rand.nextInt(Integer.MAX_VALUE);
}
}catch (Throwable e) {
e.printStackTrace();
}
}
}
jvm參數(shù)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
日志
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
[GC (Allocation Failure) [PSYoungGen: 2009K->503K(2560K)] 2009K->803K(9728K), 0.7943182 secs] [Times: user=0.00 sys=0.00, real=0.79 secs]
[GC (Allocation Failure) [PSYoungGen: 2272K->432K(2560K)] 2572K->2214K(9728K), 0.0020218 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2448K->352K(2560K)] 4230K->3122K(9728K), 0.0017173 secs] [Times: user=0.11 sys=0.02, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1380K->0K(2560K)] [ParOldGen: 6722K->2502K(7168K)] 8102K->2502K(9728K), [Metaspace: 2657K->2657K(1056768K)], 0.0039763 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2016K->0K(2560K)] [ParOldGen: 6454K->6454K(7168K)] 8471K->6454K(9728K), [Metaspace: 2658K->2658K(1056768K)], 0.0049598 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 6454K->6454K(9728K), 0.0008614 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] /注意這里:PSYoungGen/[ParOldGen: 6454K->6440K(7168K)]/注意這里 ParOldGen/ 6454K->6440K(9728K), [Metaspace: 2658K->2658K(1056768K)], 0.0055542 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.lun.jvm.GCDemo.main(GCDemo.java:22)
Heap
PSYoungGen total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14810,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 6440K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 89% used [0x00000000ff600000,0x00000000ffc4a1c8,0x00000000ffd00000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
日志解讀:
1.使用Parallel Scavenge時(shí)再愈,年輕代是表示為PSYoungGen,老年代是表示為ParOldGen榜苫。
Serial Old 收集器
記憶
- 介紹
- Serial Old是Serial收集器的<font color=red>老年代版本</font>,它同樣是一個(gè)<font color=red>單線程收集器</font>翎冲,使用<font color=red>標(biāo)記-整理算法</font>垂睬。
- 使用場(chǎng)景
- 這個(gè)收集器的主要意義也是供客戶端模式下的HotSpot虛擬機(jī)使用。
- 如果在服務(wù)端模式抗悍,驹饺,它也可能有兩種用途:
- 一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用。
- 需要說(shuō)明一下缴渊,Parallel Scavenge收集器架構(gòu)中本身有PS MarkSweep收集器來(lái)進(jìn)行老年代收集赏壹,并非直接調(diào)用Serial Old收集器,但是這個(gè)PS MarkSweep收集器與Serial Old的實(shí)現(xiàn)幾乎是一樣的衔沼,所以在官方的許多資料中都是直接以Serial Old代替PS MarkSweep進(jìn)行講解蝌借,這里筆者也采用這種方式昔瞧。
- 另外一種就是作為CMS收集器發(fā)生失敗時(shí)的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用菩佑。
- 一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用。
-
運(yùn)行過(guò)程
image.png
例子
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Random rand = new Random(System.nanoTime());
try {
String str = "Hello, World";
while(true) {
str += str + rand.nextInt(Integer.MAX_VALUE) + rand.nextInt(Integer.MAX_VALUE);
}
}catch (Throwable e) {
e.printStackTrace();
}
}
}
jvm參數(shù)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC
日志
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation
-XX:+UseSerialGC /注意這里 DefNew Default New Generation/
[GC (Allocation Failure) [DefNew: 2346K->320K(3072K), 0.0012956 secs] 2346K->1030K(9920K), 0.0013536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2888K->0K(3072K), 0.0013692 secs] 3598K->2539K(9920K), 0.0014059 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2065K->0K(3072K), 0.0011613 secs] 4604K->4550K(9920K), 0.0011946 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2056K->0K(3072K), 0.0010394 secs] 6606K->6562K(9920K), 0.0010808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2011K->2011K(3072K), 0.0000124 secs][Tenured /注意這里old(老年代)是Tenured/: 6562K->2537K(6848K), 0.0021691 secs] 8574K->2537K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0024399 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2059K->2059K(3072K), 0.0000291 secs][Tenured: 6561K->6561K(6848K), 0.0012330 secs] 8620K->6561K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0012888 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 6561K->6547K(6848K), 0.0017784 secs] 6561K->6547K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0018111 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.lun.jvm.GCDemo.main(GCDemo.java:23)
Heap
def new generation total 3072K, used 105K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61a7c8, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 6547K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 95% used [0x00000000ff950000, 0x00000000fffb4c30, 0x00000000fffb4e00, 0x0000000100000000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
日志解讀:
1.使用SerialGC時(shí)自晰,年輕代是表示為:DefNew(DefNew Default New Generation),老年代表示為:Tenured擎鸠;
Parallel Old收集器
記憶
- 介紹
- Parallel Old是<font color=red>Parallel Scavenge收集器的老年代版本</font>缀磕,支持<font color=red>多線程并發(fā)收集</font>缘圈,基于<font color=red>標(biāo)記-整理算法</font>實(shí)現(xiàn)劣光。
- 配合Parallel Scavenge收集器使用
- 這個(gè)收集器是直到JDK 6時(shí)才開始提供的,在此之前糟把,新生代的Parallel Scavenge收集器一直處于相當(dāng)尷尬的狀態(tài)绢涡,原因是如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外別無(wú)選擇遣疯,其他表現(xiàn)良好的老年代收集器雄可,如CMS無(wú)法與它配合工作。
- 由于老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的“拖累”缠犀,使用Parallel Scavenge收集器也未必能在整體上獲得吞吐量最大化的效果数苫。
- 同樣,由于單線程的老年代收集中無(wú)法充分利用服務(wù)器多處理器的并行處理能力辨液,在老年代內(nèi)存空間很大而且硬件規(guī)格比較高級(jí)的運(yùn)行環(huán)境中虐急,這種組合的總吞吐量甚至不一定比ParNew加CMS的組合來(lái)得優(yōu)秀。
- 適用場(chǎng)合
- 在注重吞吐量或者處理器資源較為稀缺的場(chǎng)合滔迈,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組合止吁。
-
運(yùn)行過(guò)程
image.png
例子
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Random rand = new Random(System.nanoTime());
try {
String str = "Hello, World";
while(true) {
str += str + rand.nextInt(Integer.MAX_VALUE) + rand.nextInt(Integer.MAX_VALUE);
}
}catch (Throwable e) {
e.printStackTrace();
}
}
}
jvm參數(shù)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC
日志
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelOldGC
[GC (Allocation Failure) [PSYoungGen: 1979K->480K(2560K)] 1979K->848K(9728K), 0.0007724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2205K->480K(2560K)] 2574K->2317K(9728K), 0.0008700 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2446K->496K(2560K)] 4284K->3312K(9728K), 0.0010374 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1499K->0K(2560K)] [ParOldGen: 6669K->2451K(7168K)] 8168K->2451K(9728K), [Metaspace: 2658K->2658K(1056768K)], 0.0043327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1966K->0K(2560K)] [ParOldGen: 6304K->6304K(7168K)] 8270K->6304K(9728K), [Metaspace: 2658K->2658K(1056768K)], 0.0021269 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 6304K->6304K(9728K), 0.0004841 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 6304K->6290K(7168K)] 6304K->6290K(9728K), [Metaspace: 2658K->2658K(1056768K)], 0.0058149 secs] [Times: user=0.11 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.lun.jvm.GCDemo.main(GCDemo.java:22)
Heap
PSYoungGen total 2560K, used 81K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd14768,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 6290K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 87% used [0x00000000ff600000,0x00000000ffc24b70,0x00000000ffd00000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
日志解讀
1.使用Parallel Old GC時(shí),年輕代是表示為: PSYoungGen燎悍,老年代表示為:ParOldGen敬惦。
CMS收集器
記憶
- 介紹
- CMS(Concurrent Mark Sweep)收集器是一種以<font color=apple green>獲取最短回收停頓時(shí)間</font>為目標(biāo)的收集器。
- 使用場(chǎng)景
- 較為關(guān)注服務(wù)的響應(yīng)速度應(yīng)用谈山。
- 目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的B/S系統(tǒng)的服務(wù)端上俄删,這類應(yīng)用通常都會(huì)較為關(guān)注服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間盡可能短奏路,以給用戶帶來(lái)良好的交互體驗(yàn)畴椰。CMS收集器就非常符合這類應(yīng)用的需求。
- 較為關(guān)注服務(wù)的響應(yīng)速度應(yīng)用谈山。
- 運(yùn)作過(guò)程
- 從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于<font color=red>標(biāo)記-清除算法</font>實(shí)現(xiàn)的思劳,它的運(yùn)作過(guò)程相對(duì)于前面幾種收集器來(lái)說(shuō)要更復(fù)雜一些迅矛,整個(gè)過(guò)程分為四個(gè)步驟,包括:
- 初始標(biāo)記(CMS initial mark)
- 只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)的對(duì)象潜叛,速度很快秽褒,仍然<font color=red>需要暫停所有的工作線程</font>壶硅。
- 并發(fā)標(biāo)記(CMS concurrent mark)
- 并發(fā)標(biāo)記階段就是從<font color=green>GC Roots的直接關(guān)聯(lián)對(duì)象</font>開始<font color=red>遍歷整個(gè)對(duì)象圖的過(guò)程</font>,這個(gè)過(guò)程耗時(shí)較長(zhǎng)但是<font color=red>不需要停頓用戶線程</font>销斟,可以與<font color=red>垃圾收集線程一起并發(fā)運(yùn)行</font>庐椒。
- 重新標(biāo)記(CMS remark)
- 為了修正在并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄
- 仍然<font color=red>需要暫停所有的工作線程</font>蚂踊。
- 由于并發(fā)標(biāo)記時(shí)约谈,用戶線程依然運(yùn)行,因此在正式清理前犁钟,再做修正棱诱。
- 這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長(zhǎng)一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短涝动。
- 并發(fā)清除(CMS concurrent sweep)
- 清除GCRoots不可達(dá)對(duì)象迈勋,和用戶線程一起工作,<font color=red>不需要暫停工作線程</font>醋粟。
- 基于標(biāo)記結(jié)果靡菇,直接清理對(duì)象。
- 初始標(biāo)記(CMS initial mark)
- 從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于<font color=red>標(biāo)記-清除算法</font>實(shí)現(xiàn)的思劳,它的運(yùn)作過(guò)程相對(duì)于前面幾種收集器來(lái)說(shuō)要更復(fù)雜一些迅矛,整個(gè)過(guò)程分為四個(gè)步驟,包括:
- 優(yōu)點(diǎn)
- 并發(fā)收集米愿、低停頓
- 由于耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過(guò)程中厦凤,垃圾收集線程可以和用戶現(xiàn)在一起并發(fā)工作,所以總體上來(lái)看CMS 收集器的內(nèi)存回收和用戶線程是一起并發(fā)地執(zhí)行育苟。
- 并發(fā)收集米愿、低停頓
- 缺點(diǎn)
- 并發(fā)執(zhí)行较鼓,對(duì)CPU資源壓力大,所以對(duì)處理器非常敏感宙搬。
- 在并發(fā)階段笨腥,它雖然不會(huì)導(dǎo)致用戶線程停頓,但卻會(huì)因?yàn)檎加昧艘徊糠志€程(或者說(shuō)處理器的計(jì)算能力)而導(dǎo)致應(yīng)用程序變慢勇垛,降低總吞吐量脖母。
- CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(處理器核心數(shù)量+3)/4
- 也就是說(shuō),如果處理器核心數(shù)在四個(gè)或以上闲孤,并發(fā)回收時(shí)垃圾收集線程只占用不超過(guò)25%的處理器運(yùn)算資源谆级,并且會(huì)隨著處理器核心數(shù)量的增加而下降。
- 但是當(dāng)處理器核心數(shù)量不足四個(gè)時(shí)讼积,CMS對(duì)用戶程序的影響就可能變得很大肥照。
- 如果應(yīng)用本來(lái)的處理器負(fù)載就很高,還要分出一半的運(yùn)算能力去執(zhí)行收集器線程勤众,就可能導(dǎo)致用戶程序的執(zhí)行速度忽然大幅降低舆绎。
- 為了緩解這種情況,虛擬機(jī)提供了一種稱為“增量式并發(fā)收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器變種们颜,所做的事情和以前單核處理器年代PC機(jī)操作系統(tǒng)靠搶占式多任務(wù)來(lái)模擬多核并行多任務(wù)的思想一樣吕朵,是在并發(fā)標(biāo)記猎醇、清理的時(shí)候讓收集器線程、用戶線程交替運(yùn)行努溃,盡量減少垃圾收集線程的獨(dú)占資源的時(shí)間硫嘶,這樣整個(gè)垃圾收集的過(guò)程會(huì)更長(zhǎng),但對(duì)用戶程序的影響就會(huì)顯得較少一些梧税,直觀感受是速度變慢的時(shí)間更多了沦疾,但速度下降幅度就沒有那么明顯。實(shí)踐證明增量式的CMS收集器效果很一般第队,從JDK 7開始哮塞,i-CMS模式已經(jīng)被聲明為“deprecated”,即已過(guò)時(shí)不再提倡用戶使用斥铺,到JDK 9發(fā)布后i-CMS模式被完全廢棄彻桃。
- CMS收集器無(wú)法處理“浮動(dòng)垃圾”(Floating Garbage),有可能出現(xiàn)“Con-current Mode Failure”失敗進(jìn)而導(dǎo)致另一次完全“Stop The World”的Full GC的產(chǎn)生晾蜘。
- 在CMS的并發(fā)標(biāo)記和并發(fā)清理階段,用戶線程是還在繼續(xù)運(yùn)行的眠屎,程序在運(yùn)行自然就還會(huì)伴隨有新的垃圾對(duì)象不斷產(chǎn)生剔交,但這一部分垃圾對(duì)象是出現(xiàn)在標(biāo)記過(guò)程結(jié)束以后,CMS無(wú)法在當(dāng)次收集中處理掉它們改衩,只好留待下一次垃圾收集時(shí)再清理掉岖常。這一部分垃圾就稱為“浮動(dòng)垃圾”。
- 同樣也是由于在垃圾收集階段用戶線程還需要持續(xù)運(yùn)行葫督,那就還需要預(yù)留足夠內(nèi)存空間提供給用戶線程使用竭鞍,因此CMS收集器不能像其他收集器那樣等待到老年代幾乎完全被填滿了再進(jìn)行收集,必須預(yù)留一部分空間供并發(fā)收集時(shí)的程序運(yùn)作使用橄镜。
- 在JDK 5的默認(rèn)設(shè)置下偎快,CMS收集器當(dāng)老年代使用了68%的空間后就會(huì)被激活,這是一個(gè)偏保守的設(shè)置洽胶,如果在實(shí)際應(yīng)用中老年代增長(zhǎng)并不是太快晒夹,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccu-pancyFraction的值來(lái)提高CMS的觸發(fā)百分比,降低內(nèi)存回收頻率姊氓,獲取更好的性能丐怯。
- 到了JDK 6時(shí),CMS收集器的啟動(dòng)閾值就已經(jīng)默認(rèn)提升至92%翔横。但這又會(huì)更容易面臨另一種風(fēng)險(xiǎn):
- 要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足程序分配新對(duì)象的需要读跷,就會(huì)出現(xiàn)一次“并發(fā)失敗”(Concurrent Mode Failure),這時(shí)候虛擬機(jī)將不得不啟動(dòng)后備預(yù)案:凍結(jié)用戶線程的執(zhí)行禾唁,臨時(shí)啟用Serial Old收集器來(lái)重新進(jìn)行老年代的垃圾收集效览,但這樣停頓時(shí)間就很長(zhǎng)了些膨。
- 所以參數(shù)-XX:CMSInitiatingOccupancyFraction設(shè)置得太高將會(huì)很容易導(dǎo)致大量的并發(fā)失敗產(chǎn)生,性能反而降低钦铺,用戶應(yīng)在生產(chǎn)環(huán)境中根據(jù)實(shí)際應(yīng)用情況來(lái)權(quán)衡設(shè)置订雾。
- CMS是一款基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生
- 空間碎片過(guò)多時(shí)矛洞,將會(huì)給大對(duì)象分配帶來(lái)很大麻煩
- 往往會(huì)出現(xiàn)老年代還有很多剩余空間洼哎,但就是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,而不得不提前觸發(fā)一次Full GC的情況沼本。
- 為了解決這個(gè)問(wèn)題噩峦,CMS收集器提供了一個(gè)-XX:+UseCMS-CompactAtFullCollection開關(guān)參數(shù)(默認(rèn)是開啟的,此參數(shù)從JDK 9開始廢棄)抽兆,用于在CMS收集器不得不進(jìn)行Full GC時(shí)開啟內(nèi)存碎片的合并整理過(guò)程识补。
- 由于這個(gè)內(nèi)存整理必須移動(dòng)存活對(duì)象,(在Shenandoah和ZGC出現(xiàn)前)是無(wú)法并發(fā)的辫红。這樣空間碎片問(wèn)題是解決了凭涂,但停頓時(shí)間又會(huì)變長(zhǎng)。
- 因此虛擬機(jī)設(shè)計(jì)者們還提供了另外一個(gè)參數(shù)-XX:CMSFullGCsBefore-Compaction(此參數(shù)從JDK 9開始廢棄)贴妻,這個(gè)參數(shù)的作用是要求CMS收集器在執(zhí)行過(guò)若干次(數(shù)量由參數(shù)值決定)不整理空間的Full GC之后切油,下一次進(jìn)入Full GC前會(huì)先進(jìn)行碎片整理(默認(rèn)值為0,表示每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理)名惩。
- 空間碎片過(guò)多時(shí)矛洞,將會(huì)給大對(duì)象分配帶來(lái)很大麻煩
- 并發(fā)執(zhí)行较鼓,對(duì)CPU資源壓力大,所以對(duì)處理器非常敏感宙搬。
CMS收集器運(yùn)行示意圖
Garbage First 收集器
歷史
? Garbage First(簡(jiǎn)稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果澎胡,它開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。早在JDK 7剛剛確立項(xiàng)目目標(biāo)娩鹉、Oracle公司制定的JDK 7 RoadMap里面攻谁,G1收集器就被視作JDK 7中HotSpot虛擬機(jī)的一項(xiàng)重要進(jìn)化特征。從JDK 6 Update 14開始就有Early Access版本的G1收集器供開發(fā)人員實(shí)驗(yàn)和試用弯予,但由此開始G1收集器的“實(shí)驗(yàn)狀態(tài)”(Experimental)持續(xù)了數(shù)年時(shí)間戚宦,直至JDK 7 Update 4,Oracle才認(rèn)為它達(dá)到足夠成熟的商用程度熙涤,移除了“Experimental”的標(biāo)識(shí)阁苞;到了JDK 8 Update 40的時(shí)候,G1提供并發(fā)的類卸載的支持祠挫,補(bǔ)全了其計(jì)劃功能的最后一塊拼圖那槽。這個(gè)版本以后的G1收集器才被Oracle官方稱為“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。
? G1是一款主要面向服務(wù)端應(yīng)用的垃圾收集器等舔。HotSpot開發(fā)團(tuán)隊(duì)最初賦予它的期望是(在比較長(zhǎng)期的)未來(lái)可以替換掉JDK 5中發(fā)布的CMS收集器∩Ь模現(xiàn)在這個(gè)期望目標(biāo)已經(jīng)實(shí)現(xiàn)過(guò)半了,JDK 9發(fā)布之日慌植,G1宣告取代Parallel Scavenge加Parallel Old組合甚牲,成為服務(wù)端模式下的默認(rèn)垃圾收集器义郑,而CMS則淪落至被聲明為不推薦使用(Deprecate)的收集器[1]。如果對(duì)JDK 9及以上版本的HotSpot虛擬機(jī)使用參數(shù)-XX:+UseConcMarkSweepGC來(lái)開啟CMS收集器的話丈钙,用戶會(huì)收到一個(gè)警告信息非驮,提示CMS未來(lái)將會(huì)被廢棄:
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
? 但作為一款曾被廣泛運(yùn)用過(guò)的收集器,經(jīng)過(guò)多個(gè)版本的開發(fā)迭代后雏赦,CMS(以及之前幾款收集器)的代碼與HotSpot的內(nèi)存管理劫笙、執(zhí)行、編譯星岗、監(jiān)控等子系統(tǒng)都有千絲萬(wàn)縷的聯(lián)系填大,這是歷史原因?qū)е碌模⒉环下氊?zé)分離的設(shè)計(jì)原則俏橘。為此允华,規(guī)劃JDK 10功能目標(biāo)時(shí),HotSpot虛擬機(jī)提出了“統(tǒng)一垃圾收集器接口”[2]寥掐,將內(nèi)存回收的“行為”與“實(shí)現(xiàn)”進(jìn)行分離靴寂,CMS以及其他收集器都重構(gòu)成基于這套接口的一種實(shí)現(xiàn)。以此為基礎(chǔ)曹仗,日后要移除或者加入某一款收集器榨汤,都會(huì)變得容易許多,風(fēng)險(xiǎn)也可以控制怎茫,這算是在為CMS退出歷史舞臺(tái)鋪下最后的道路了。
? 作為CMS收集器的替代者和繼承人妓灌,設(shè)計(jì)者們希望做出一款能夠建立起“停頓時(shí)間模型”(Pause Prediction Model)的收集器轨蛤,停頓時(shí)間模型的意思是能夠支持指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間大概率不超過(guò)N毫秒這樣的目標(biāo)虫埂,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的中軟實(shí)時(shí)垃圾收集器特征了祥山。
? 那具體要怎么做才能實(shí)現(xiàn)這個(gè)目標(biāo)呢?首先要有一個(gè)思想上的改變掉伏,在G1收集器出現(xiàn)之前的所有其他收集器缝呕,包括CMS在內(nèi),垃圾收集的目標(biāo)范圍要么是整個(gè)新生代(Minor GC)斧散,要么就是整個(gè)老年代(Major GC)供常,再要么就是整個(gè)Java堆(Full GC)。而G1跳出了這個(gè)樊籠鸡捐,它可以面向堆內(nèi)存任何部分來(lái)組成回收集(Collection Set栈暇,一般簡(jiǎn)稱CSet)進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代箍镜,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多源祈,回收收益最大煎源,這就是G1收集器的Mixed GC模式。
記憶
- 介紹
- G1是一款主要面向服務(wù)端應(yīng)用的垃圾收集器香缺,應(yīng)用在多處理器和大容量?jī)?nèi)存環(huán)境中手销,在實(shí)現(xiàn)高吞吐量的同時(shí),盡可能的滿足垃圾收集暫停時(shí)間的要求图张。
- G1不再堅(jiān)持固定大小以及固定數(shù)量的分代區(qū)域劃分锋拖,而是把連續(xù)的Java堆<font color=red>劃分</font>為<font color=apple green>多個(gè)大小相等的獨(dú)立區(qū)域(Region)</font>,<font color=red>每一個(gè)Region都可以根據(jù)需要埂淮,扮演新生代的Eden空間姑隅、Survivor空間,或者老年代空間</font>倔撞。收集器能夠<font color=apple green>對(duì)扮演不同角色的Region采用不同的策略去處理</font>讲仰,這樣無(wú)論是新創(chuàng)建的對(duì)象還是已經(jīng)存活了一段時(shí)間、熬過(guò)多次收集的舊對(duì)象都能獲取很好的收集效果痪蝇。
- 如何存儲(chǔ)大對(duì)象
- Humongous區(qū)域鄙陡,專門用來(lái)存儲(chǔ)大對(duì)象。
- G1認(rèn)為只要大小超過(guò)了一個(gè)Region容量一半的對(duì)象即可判定為大對(duì)象躏啰。每個(gè)Region的大小可以通過(guò)參數(shù)-XX:G1HeapRegionSize設(shè)定趁矾,取值范圍為1MB~32MB,且應(yīng)為2的N次冪给僵。
- 而對(duì)于那些超過(guò)了整個(gè)Region容量的超級(jí)大對(duì)象毫捣,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region之中,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分來(lái)進(jìn)行看待帝际。
- 特點(diǎn)
- Eden蔓同,Survivor和Tenured等內(nèi)存區(qū)域不再是連續(xù)的了,而是變成了一個(gè)個(gè)大小一樣的region蹲诀,宏觀上看G1之中不再區(qū)分年輕代和老年代斑粱。
- 雖然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了脯爪,它們都是一系列區(qū)域(不需要連續(xù))的動(dòng)態(tài)集合则北。
- G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗?lt;font color=red>將Region作為單次回收的最小單元痕慢,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍</font>尚揣,這樣可以<font color=apple green>有計(jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集</font>。
- 更具體的處理思路是讓G1收集器去跟蹤各個(gè)Region里面的垃圾堆積的“價(jià)值”大小守屉,價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值惑艇,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間(使用參數(shù)-XX:MaxGCPauseMillis指定,默認(rèn)值是200毫秒)滨巴,優(yōu)先處理回收價(jià)值收益最大的那些Region思灌,這也就是“Garbage First”名字的由來(lái)。這種使用Region劃分內(nèi)存空間恭取,以及具有優(yōu)先級(jí)的區(qū)域回收方式泰偿,保證了G1收集器在有限的時(shí)間內(nèi)獲取盡可能高的收集效率。
- 可以由用戶指定期望的停頓時(shí)間是G1收集器很強(qiáng)大的一個(gè)功能蜈垮,設(shè)置不同的期望停頓時(shí)間耗跛,可使得G1在不同應(yīng)用場(chǎng)景中取得關(guān)注吞吐量和關(guān)注延遲之間的最佳平衡。
- 這里設(shè)置的“期望值”必須是符合實(shí)際的攒发,不能異想天開调塌,畢竟G1是要凍結(jié)用戶線程來(lái)復(fù)制對(duì)象的,這個(gè)停頓時(shí)間再怎么低也得有個(gè)限度惠猿。
- 它默認(rèn)的停頓目標(biāo)為兩百毫秒羔砾,一般來(lái)說(shuō),回收階段占到幾十到一百甚至接近兩百毫秒都很正常偶妖。
- 但如果我們把停頓時(shí)間調(diào)得非常低姜凄,譬如設(shè)置為二十毫秒,很可能出現(xiàn)的結(jié)果就是由于停頓目標(biāo)時(shí)間太短趾访,導(dǎo)致每次選出來(lái)的回收集只占堆內(nèi)存很小的一部分态秧,收集器收集的速度逐漸跟不上分配器分配的速度,導(dǎo)致垃圾慢慢堆積扼鞋。很可能一開始收集器還能從空閑的堆內(nèi)存中獲得一些喘息的時(shí)間申鱼,但應(yīng)用運(yùn)行時(shí)間一長(zhǎng)就不行了,最終占滿堆引發(fā)Full GC反而降低性能云头,所以通常把期望停頓時(shí)間設(shè)置為一兩百毫秒或者兩三百毫秒會(huì)是比較合理的润讥。
- 從G1開始,最先進(jìn)的垃圾收集器的設(shè)計(jì)導(dǎo)向都不約而同地變?yōu)樽非竽軌驊?yīng)付應(yīng)用的內(nèi)存分配速率(Allocation Rate)盘寡,而不追求一次把整個(gè)Java堆全部清理干凈。
- 這樣撮慨,應(yīng)用在分配竿痰,同時(shí)收集器在收集,只要收集的速度能跟得上對(duì)象分配的速度砌溺,那一切就能運(yùn)作得很完美影涉。這種新的收集器設(shè)計(jì)思路從工程實(shí)現(xiàn)上看是從G1開始興起的,所以說(shuō)G1是收集器技術(shù)發(fā)展的一個(gè)里程碑规伐。
- G1能充分利用多CPU蟹倾、多核環(huán)境硬件優(yōu)勢(shì),盡量縮短STW
- G1雖然也是分代收集器,但整個(gè)內(nèi)存分區(qū)不存在物理上的年輕代與老年代的區(qū)別鲜棠,也不需要完全獨(dú)立的survivor(to space)堆做復(fù)制準(zhǔn)備肌厨。G1只有邏輯上的分代概念,或者說(shuō)每個(gè)分區(qū)都可能隨G1的運(yùn)行在不同代之間前后切換豁陆。
- Eden蔓同,Survivor和Tenured等內(nèi)存區(qū)域不再是連續(xù)的了,而是變成了一個(gè)個(gè)大小一樣的region蹲诀,宏觀上看G1之中不再區(qū)分年輕代和老年代斑粱。
- G1將堆內(nèi)存“化整為零”的“解題思路”柑爸,看起來(lái)似乎沒有太多令人驚訝之處,也完全不難理解盒音,但其中的實(shí)現(xiàn)細(xì)節(jié)可是遠(yuǎn)遠(yuǎn)沒有想象中那么簡(jiǎn)單表鳍,否則就不會(huì)從2004年Sun實(shí)驗(yàn)室發(fā)表第一篇關(guān)于G1的論文后一直拖到2012年4月JDK 7 Update 4發(fā)布,用將近10年時(shí)間才倒騰出能夠商用的G1收集器來(lái)祥诽。G1收集器至少有(不限于)以下這些關(guān)鍵的細(xì)節(jié)問(wèn)題需要妥善解決:
- 譬如譬圣,將Java堆分成多個(gè)獨(dú)立Region后,Region里面存在的跨Region引用對(duì)象如何解決雄坪?
- 使用記憶集避免全堆作為GC Roots掃描厘熟,但在G1收集器上記憶集的應(yīng)用其實(shí)要復(fù)雜很多,它的每個(gè)Region都維護(hù)有自己的記憶集诸衔,這些記憶集會(huì)記錄下別的Region指向自己的指針盯漂,并標(biāo)記這些指針?lè)謩e在哪些卡頁(yè)的范圍之內(nèi)。G1的記憶集在存儲(chǔ)結(jié)構(gòu)的本質(zhì)上是一種哈希表笨农,Key是別的Region的起始地址就缆,Value是一個(gè)集合,里面存儲(chǔ)的元素是卡表的索引號(hào)谒亦。這種“雙向”的卡表結(jié)構(gòu)(卡表是“我指向誰(shuí)”竭宰,這種結(jié)構(gòu)還記錄了“誰(shuí)指向我”)比原來(lái)的卡表實(shí)現(xiàn)起來(lái)更復(fù)雜,同時(shí)由于Region數(shù)量比傳統(tǒng)收集器的分代數(shù)量明顯要多得多份招,因此G1收集器要比其他的傳統(tǒng)垃圾收集器有著更高的內(nèi)存占用負(fù)擔(dān)切揭。根據(jù)經(jīng)驗(yàn),G1至少要耗費(fèi)大約相當(dāng)于Java堆容量10%至20%的額外內(nèi)存來(lái)維持收集器工作锁摔。
- 譬如廓旬,在并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾地運(yùn)行?
- 這里首先要解決的是用戶線程改變對(duì)象引用關(guān)系時(shí)谐腰,必須保證其不能打破原本的對(duì)象圖結(jié)構(gòu)孕豹,導(dǎo)致標(biāo)記結(jié)果出現(xiàn)錯(cuò)誤,該問(wèn)題的解決辦法筆者已經(jīng)抽出獨(dú)立小節(jié)來(lái)講解過(guò)(見3.4.6節(jié)):CMS收集器采用增量更新算法實(shí)現(xiàn)十气,而G1收集器則是通過(guò)原始快照(SATB)算法來(lái)實(shí)現(xiàn)的励背。此外,垃圾收集對(duì)用戶線程的影響還體現(xiàn)在回收過(guò)程中新創(chuàng)建對(duì)象的內(nèi)存分配上砸西,程序要繼續(xù)運(yùn)行就肯定會(huì)持續(xù)有新對(duì)象被創(chuàng)建叶眉,G1為每一個(gè)Region設(shè)計(jì)了兩個(gè)名為TAMS(Top at Mark Start)的指針址儒,把Region中的一部分空間劃分出來(lái)用于并發(fā)回收過(guò)程中的新對(duì)象分配,并發(fā)回收時(shí)新分配的對(duì)象地址都必須要在這兩個(gè)指針位置以上衅疙。G1收集器默認(rèn)在這個(gè)地址以上的對(duì)象是被隱式標(biāo)記過(guò)的莲趣,即默認(rèn)它們是存活的,不納入回收范圍炼蛤。與CMS中的“Concurrent Mode Failure”失敗會(huì)導(dǎo)致Full GC類似妖爷,如果內(nèi)存回收的速度趕不上內(nèi)存分配的速度,G1收集器也要被迫凍結(jié)用戶線程執(zhí)行理朋,導(dǎo)致Full GC而產(chǎn)生長(zhǎng)時(shí)間“Stop The World”絮识。
- 譬如,怎樣建立起可靠的停頓預(yù)測(cè)模型?
- 用戶通過(guò)-XX:MaxGCPauseMillis參數(shù)指定的停頓時(shí)間只意味著垃圾收集發(fā)生之前的期望值,但G1收集器要怎么做才能滿足用戶的期望呢田绑?G1收集器的停頓預(yù)測(cè)模型是以衰減均值(Decaying Average)為理論基礎(chǔ)來(lái)實(shí)現(xiàn)的,在垃圾收集過(guò)程中彼念,G1收集器會(huì)記錄每個(gè)Region的回收耗時(shí)、每個(gè)Region記憶集里的臟卡數(shù)量等各個(gè)可測(cè)量的步驟花費(fèi)的成本浅萧,并分析得出平均值逐沙、標(biāo)準(zhǔn)偏差、置信度等統(tǒng)計(jì)信息洼畅。這里強(qiáng)調(diào)的“衰減平均值”是指它會(huì)比普通的平均值更容易受到新數(shù)據(jù)的影響吩案,平均值代表整體平均狀態(tài),但衰減平均值更準(zhǔn)確地代表“最近的”平均狀態(tài)帝簇。換句話說(shuō)徘郭,Region的統(tǒng)計(jì)狀態(tài)越新越能決定其回收的價(jià)值。然后通過(guò)這些信息預(yù)測(cè)現(xiàn)在開始回收的話丧肴,由哪些Region組成回收集才可以在不超過(guò)期望停頓時(shí)間的約束下獲得最高的收益残揉。
- 譬如譬圣,將Java堆分成多個(gè)獨(dú)立Region后,Region里面存在的跨Region引用對(duì)象如何解決雄坪?
- 如果我們不去計(jì)算用戶線程運(yùn)行過(guò)程中的動(dòng)作(如使用寫屏障維護(hù)記憶集的操作),G1收集器的運(yùn)作過(guò)程大致可劃分為以下四個(gè)步驟:
- 初始標(biāo)記(Initial Marking):
- 僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象芋浮,并且修改TAMS指針的值抱环,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對(duì)象纸巷。這個(gè)階段需要停頓線程江醇,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的何暇,所以G1收集器在這個(gè)階段實(shí)際并沒有額外的停頓。
- 并發(fā)標(biāo)記(Concurrent Marking):
- 從GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析凛驮,遞歸掃描整個(gè)堆里的對(duì)象圖裆站,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行宏胯。當(dāng)對(duì)象圖掃描完成以后羽嫡,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象。
- 最終標(biāo)記(Final Marking):
- 對(duì)用戶線程做另一個(gè)短暫的暫停肩袍,用于處理并發(fā)階段結(jié)束后仍遺留下來(lái)的最后那少量的SATB記錄杭棵。
- 篩選回收(Live Data Counting and Evacuation):
- 負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序氛赐,根據(jù)用戶所期望的停頓時(shí)間來(lái)制定回收計(jì)劃魂爪,可以自由選擇任意多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中艰管,再清理掉整個(gè)舊Region的全部空間滓侍。這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程牲芋,由多條收集器線程并行完成的撩笆。
- 從上述階段的描述可以看出,G1收集器除了并發(fā)標(biāo)記外缸浦,其余階段也是要完全暫停用戶線程的夕冲,換言之,它并非純粹地追求低延遲裂逐,官方給它設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量歹鱼,所以才能擔(dān)當(dāng)起“全功能收集器”的重任與期望。
- 初始標(biāo)記(Initial Marking):
- G1收集器的設(shè)計(jì)目標(biāo)是取代CMS收集器絮姆,它同CMS相比醉冤,在以下方面表現(xiàn)的更出色:
- G1是一個(gè)有整理內(nèi)存過(guò)程的垃圾收集器,不會(huì)產(chǎn)生很多內(nèi)存碎片篙悯。
- G1的Stop The World(STW)更可控蚁阳,G1在停頓時(shí)間上添加了預(yù)測(cè)機(jī)制,用戶可以指定期望停頓時(shí)間鸽照。
- CMS垃圾收集器雖然減少了暫停應(yīng)用程序的運(yùn)行時(shí)間螺捐,但是它還是存在著內(nèi)存碎片問(wèn)題。于是矮燎,為了去除內(nèi)存碎片問(wèn)題定血,同時(shí)又保留CMS垃圾收集器低暫停時(shí)間的優(yōu)點(diǎn),JAVA7發(fā)布了一個(gè)新的垃圾收集器-G1垃圾收集器诞外。
? G1是在2012年才在jdk1.7u4中可用澜沟。oracle官方計(jì)劃在JDK9中將G1變成默認(rèn)的垃圾收集器以替代CMS。它是一款面向服務(wù)端應(yīng)用的收集器峡谊,主要應(yīng)用在多CPU和大內(nèi)存服務(wù)器環(huán)境下茫虽,極大的減少垃圾收集的停頓時(shí)間刊苍,全面提升服務(wù)器的性能,逐步替換java8以前的CMS收集器濒析。
? 現(xiàn)在這個(gè)期望目標(biāo)已經(jīng)實(shí)現(xiàn)過(guò)半了正什,JDK 9發(fā)布之日,G1宣告取代Parallel Scavenge加Parallel Old組合号杏,成為服務(wù)端模式下的默認(rèn)垃圾收集器婴氮,而CMS則淪落至被聲明為不推薦使用(Deprecate)的收集器。
? 但是G1相對(duì)于CMS仍然不是占全方位盾致、壓倒性優(yōu)勢(shì)的主经,從它出現(xiàn)幾年仍不能在所有應(yīng)用場(chǎng)景中代替CMS就可以得知這個(gè)結(jié)論。比起CMS绰上,G1的弱項(xiàng)也可以列舉出不少:
- 如在用戶程序運(yùn)行過(guò)程中旨怠,G1無(wú)論是為了垃圾收集產(chǎn)生的內(nèi)存占用(Footprint)還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載(Overload)都要比CMS要高。
- 就內(nèi)存占用來(lái)說(shuō)蜈块,雖然G1和CMS都使用卡表來(lái)處理跨代指針鉴腻,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region百揭,無(wú)論扮演的是新生代還是老年代角色爽哎,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)可能會(huì)占整個(gè)堆容量的20%乃至更多的內(nèi)存空間器一;相比起來(lái)CMS的卡表就相當(dāng)簡(jiǎn)單课锌,只有唯一一份,而且只需要處理老年代到新生代的引用祈秕,反過(guò)來(lái)則不需要渺贤,由于新生代的對(duì)象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁请毛,能省下這個(gè)區(qū)域的維護(hù)開銷是很劃算的志鞍。
- 在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器各自的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載會(huì)有不同方仿,譬如它們都使用到寫屏障固棚,CMS用寫后屏障來(lái)更新維護(hù)卡表;而G1除了使用寫后屏障來(lái)進(jìn)行同樣的(由于G1的卡表結(jié)構(gòu)復(fù)雜仙蚜,其實(shí)是更煩瑣的)卡表維護(hù)操作外此洲,為了實(shí)現(xiàn)原始快照搜索(SATB)算法,還需要使用寫前屏障來(lái)跟蹤并發(fā)時(shí)的指針變化情況委粉。相比起增量更新算法呜师,原始快照搜索能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過(guò)長(zhǎng)的缺點(diǎn)贾节,但是在用戶程序運(yùn)行過(guò)程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來(lái)的額外負(fù)擔(dān)匣掸。由于G1對(duì)寫屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源趟紊,所以CMS的寫屏障實(shí)現(xiàn)是直接的同步操作,而G1就不得不將其實(shí)現(xiàn)為類似于消息隊(duì)列的結(jié)構(gòu)碰酝,把寫前屏障和寫后屏障中要做的事情都放到隊(duì)列里,然后再異步處理戴差。
? 以上的優(yōu)缺點(diǎn)對(duì)比僅僅是針對(duì)G1和CMS兩款垃圾收集器單獨(dú)某方面的實(shí)現(xiàn)細(xì)節(jié)的定性分析送爸,通常我們說(shuō)哪款收集器要更好、要好上多少暖释,往往是針對(duì)具體場(chǎng)景才能做的定量比較袭厂。按照筆者的實(shí)踐經(jīng)驗(yàn),目前在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率仍然要會(huì)優(yōu)于G1球匕,而在大內(nèi)存應(yīng)用上G1則大多能發(fā)揮其優(yōu)勢(shì)纹磺,這個(gè)優(yōu)劣勢(shì)的Java堆容量平衡點(diǎn)通常在6GB至8GB之間,當(dāng)然亮曹,以上這些也僅是經(jīng)驗(yàn)之談橄杨,不同應(yīng)用需要量體裁衣地實(shí)際測(cè)試才能得出最合適的結(jié)論,隨著HotSpot的開發(fā)者對(duì)G1的不斷優(yōu)化照卦,也會(huì)讓對(duì)比結(jié)果繼續(xù)向G1傾斜式矫。
G1收集器下的Young GC
針對(duì)Eden區(qū)進(jìn)行收集,Eden區(qū)耗盡后會(huì)被觸發(fā)役耕,主要是小區(qū)域收集+形成連續(xù)的內(nèi)存塊采转,避免內(nèi)存碎片
- Eden區(qū)的數(shù)據(jù)移動(dòng)到Survivor區(qū),假如出現(xiàn)Survivor區(qū)空間不夠瞬痘,Eden區(qū)數(shù)據(jù)會(huì)部會(huì)晉升到Old區(qū)故慈。
- Survivor區(qū)的數(shù)據(jù)移動(dòng)到新的Survivor區(qū),部會(huì)數(shù)據(jù)晉升到Old區(qū)框全。
- 最后Eden區(qū)收拾干凈了察绷,GC結(jié)束,用戶的應(yīng)用程序繼續(xù)執(zhí)行竣况。
GC之G1參數(shù)配置及和CMS的比較
-XX:+UseG1GC
-XX:G1HeapRegionSize=n:設(shè)置的G1區(qū)域的大小克婶。值是2的冪,范圍是1MB到32MB丹泉。目標(biāo)是根據(jù)最小的Java堆大小劃分出約2048個(gè)區(qū)域情萤。
-XX:MaxGCPauseMillis=n:最大GC停頓時(shí)間,這是個(gè)軟目標(biāo)摹恨,JVM將盡可能(但不保證)停頓小于這個(gè)時(shí)間筋岛。
-XX:InitiatingHeapOccupancyPercent=n:堆占用了多少的時(shí)候就觸發(fā)GC,默認(rèn)為45晒哄。
-XX:ConcGCThreads=n:并發(fā)GC使用的線程數(shù)睁宰。
-XX:G1ReservePercent=n:設(shè)置作為空閑空間的預(yù)留內(nèi)存百分比肪获,以降低目標(biāo)空間溢出的風(fēng)險(xiǎn),默認(rèn)值是10%柒傻。
開發(fā)人員僅僅需要聲明以下參數(shù)即可:
三步歸納:開始G1+設(shè)置最大內(nèi)存+設(shè)置最大停頓時(shí)間
- -XX:+UseG1GC
- -Xmx32g
- -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=n:最大GC停頓時(shí)間單位毫秒孝赫,這是個(gè)軟目標(biāo),JVM將盡可能(但不保證)停頓小于這個(gè)時(shí)間
G1和CMS比較
- G1不會(huì)產(chǎn)生內(nèi)碎片
- 是可以精準(zhǔn)控制停頓红符。該收集器是把整個(gè)堆(新生代青柄、老年代)劃分成多個(gè)固定大小的區(qū)域,每次根據(jù)允許停頓的時(shí)間去收集垃圾最多的區(qū)域预侯。