java JVM內(nèi)部結(jié)構(gòu)
java對(duì)象創(chuàng)建過(guò)程
/**
*
* 關(guān)于Java對(duì)象創(chuàng)建的過(guò)程:
* new關(guān)鍵字創(chuàng)建對(duì)象的3個(gè)步驟:
* 1.在堆內(nèi)存中創(chuàng)建出對(duì)象的實(shí)例插龄。
* 2.為對(duì)象的實(shí)例成員變量賦初值度帮。
* 3.將對(duì)象的引用返回
* 指針碰撞(前提是堆中的空間通過(guò)一個(gè)指針進(jìn)行分割,一側(cè)是已經(jīng)被占用的空間述呐,另一側(cè)是未被占用的空間)
* 空閑列表(前提是堆內(nèi)存空間中已被使用與未被使用的空間是交織在一起的,這時(shí)疹吃,虛擬機(jī)就需要通過(guò)一個(gè)列表來(lái)記錄哪些空間是可以使用的,
* 哪些空間是已被使用的西雀,接下來(lái)找出可以容納下新創(chuàng)建對(duì)象的且未被使用的空間萨驶,在此空間存放該對(duì)象,同時(shí)還要修改列表上的記錄)
* 對(duì)象在內(nèi)存中的布局:
* 1.對(duì)象頭.
* 2.實(shí)例數(shù)據(jù)(即我們?cè)谝粋€(gè)類(lèi)中所聲明的各項(xiàng)信息)
* 3.對(duì)齊填充(可選) !
* 引用訪問(wèn)對(duì)象的方式:
* 1.使用句柄的方式艇肴。
* 2.使用直接指針的方式腔呜。
*/
public class MemoryTest1 {
public static void main(String[] args) {
//-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError 設(shè)置jvm對(duì)空間最小和最大以及遇到錯(cuò)誤時(shí)把堆存儲(chǔ)文件打印出來(lái)
//打開(kāi)jvisualvm裝在磁盤(pán)上的轉(zhuǎn)存文件
List<MemoryTest1> list = new ArrayList<>();
while (true) {
list.add(new MemoryTest1());
System.gc();
}
}
}
虛擬機(jī)棧溢出測(cè)試
/**
*
* 虛擬機(jī)棧溢出測(cè)試
*/
public class MemoryTest2 {
private int length;
public int getLength() {
return length;
}
public void test() throws InterruptedException {
length++;
Thread.sleep(1);
test();
}
public static void main(String[] args) {
//測(cè)試調(diào)整虛擬機(jī)棧內(nèi)存大小為: -Xss160k,此處除了可以使用JVisuale監(jiān)控程序運(yùn)行狀況外還可以使用jconsole
MemoryTest2 memoryTest2 = new MemoryTest2();
try {
memoryTest2.test();
} catch (Throwable e) {
System.out.println(memoryTest2.getLength());//打印最終的最大棧深度為:2587
e.printStackTrace();
}
}
}
元空間溢出測(cè)試
/**
*
* 元空間內(nèi)存溢出測(cè)試
* 設(shè)置元空間大性俚俊:-XX:MaxMetaspaceSize=100m
* 關(guān)于元空間參考:https://www.infoq.cn/article/java-permgen-Removed
*/
public class MemoryTest3 {
public static void main(String[] args) {
//使用動(dòng)態(tài)代理動(dòng)態(tài)生成類(lèi)
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MemoryTest3.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, ags, proxy) -> proxy.invokeSuper(obj, ags));
System.out.println("Hello World");
enhancer.create();// java.lang.OutOfMemoryError: Metaspace
}
}
JVM命令使用
/**
*
* jmam命令的使用 -clstats<pid>進(jìn)程id to print class loader statistics
* jmap -clstats 3740
*
* jstat -gc 3740
* S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
* 512.0 512.0 0.0 0.0 24064.0 9626.0 86016.0 1004.1 4864.0 3758.2 512.0 409.1 144 0.064 0 0.000 0.064
* MC元空間總大小核畴,MU元空間已使用的大小
*/
public class MemoryTest4 {
public static void main(String[] args) {
while (true)
System.out.println("hello world");
}
//查看java進(jìn)程id jps -l
// 使用jcmd查看當(dāng)前進(jìn)程的可用參數(shù):jcmd 10368 help
//查看jvm的啟動(dòng)參數(shù) jcmd 10368 VM.flags
// 10368:-XX:CICompilerCount=3 -XX:InitialHeapSize=132120576 -XX:MaxHeapSize=2111832064 -XX:MaxNewSize=703594496
// -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44040192 -XX:OldSize=88080384 -XX:+UseCompressedClassPointers
// -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
}
JVM常用命令
jcmd (從JDK 1. 7開(kāi)始增加的命令)
1. jcmd pid VM.flags: 查看JVM的啟動(dòng)參數(shù)
2. jcmd pid help: 列出當(dāng)前運(yùn)行的Java進(jìn)程可以執(zhí)行的操作
3. jcmd pid helpJFR.dump:查看具體命令的選項(xiàng)
4. jcmd pid PerfCounter.print:看JVm性能相關(guān)的參數(shù)
5. jcmd pid VM.uptime:查有JVM的啟動(dòng)時(shí)長(zhǎng)
6. jcmd pid GC.class_ histogram: 查看系統(tǒng)中類(lèi)的統(tǒng)計(jì)信息
7. jcmd pid Thread.print: 查看線程堆棧信息
8. jcmd pid GC.heap dump filename 導(dǎo)出Heap dump文件, 導(dǎo)出的文件可以通過(guò)jvisualvm查看
9. jcmd pid VM.system_ properties:查看JVM的屬性信息
JVM內(nèi)存舉例說(shuō)明
public void method() {
Object object = new Object();
/*生成了2部分的內(nèi)存區(qū)域冲九,1)object這個(gè)引用變量谤草,因?yàn)? 是方法內(nèi)的變量,放到JVM Stack里面,2)真正Object
class的實(shí)例對(duì)象莺奸,放到Heap里面
上述 的new語(yǔ)句一共消耗12個(gè)bytes, JVM規(guī)定引用占4
個(gè)bytes (在JVM Stack)丑孩, 而空對(duì)象是8個(gè)bytes(在Heap)
方法結(jié)束后,對(duì)應(yīng)Stack中的變量馬上回收灭贷,但是Heap
中的對(duì)象要等到GC來(lái)回收温学、*/
}
JVM垃圾識(shí)別(根搜索算法( GC RootsTracing ))
在實(shí)際的生產(chǎn)語(yǔ)言中(Java、 C#等)甚疟,都是使用根搜索算法判定對(duì)象是否存活仗岖。
算法基本思路就是通過(guò)一系列的稱(chēng)為“GCRoots"的點(diǎn)作為起始進(jìn)行向下搜索,當(dāng)一個(gè)對(duì)象到GCRoots沒(méi)有任何引用鏈( Reference Chain)相連览妖,則證明此對(duì)象
是不可用的在Java語(yǔ)言中轧拄,GC Roots包括
●在VM棧(幀中的本地變量)中的引用
●方法區(qū)中的靜態(tài)引用
●JNI (即一般說(shuō)的Native方法) 中的引用
方法區(qū)
Java虛擬機(jī)規(guī)范表示可以不要求虛擬機(jī)在這區(qū)實(shí)現(xiàn)GC,這區(qū)GC的“性價(jià)比”一般比較低
在堆中,尤其是在新生代讽膏,常規(guī)應(yīng)用進(jìn)行I次GC一般可以回收70%~95%的空間紧帕,而方法區(qū)的GC效率遠(yuǎn)小于此當(dāng)前的商業(yè)JVM都有實(shí)現(xiàn)方法區(qū)的GC,主要回收兩部分內(nèi)容:廢棄常量與無(wú)用類(lèi)
主要回收兩部分內(nèi)容:廢棄常量與無(wú)用類(lèi)
-
類(lèi)回收需要滿足如下3個(gè)條件:
- 該類(lèi)所有的實(shí)例都已經(jīng)被GC,也就是JVM中不存在該Class的任何實(shí)例
- 加載該類(lèi)的ClassL oader已經(jīng)被GC
- 該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,如不能在任何地方通過(guò)反射訪問(wèn)該類(lèi)的方法
在大量使用反射桅打、動(dòng)態(tài)代理是嗜、CGLib等字節(jié)碼框架、動(dòng)態(tài)生成JSP以及OSGi這類(lèi)頻繁自定義Classloader的場(chǎng)景都需要JVM具備類(lèi)卸載的支持以保證方法區(qū)不會(huì)溢出
垃圾判斷與GC算法
-
垃圾判斷的算法
- 引用計(jì)數(shù)算法(Reference Counting)
- 根搜索算法( GC RootsTracing )
- 在實(shí)際的生產(chǎn)語(yǔ)言中(Java挺尾、 C#等)都是使用根搜索算法判定對(duì)象是否存活
- 算法基本思路就是通過(guò)一一系列的稱(chēng)為GCRoots"的點(diǎn)作為起始進(jìn)行向下搜索鹅搪,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈(Reference Chain)相連,則證明此對(duì)象是不可用的
-
在Java語(yǔ)言中遭铺,可作為GC Roots的對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象丽柿。
- 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象恢准。
- 方法區(qū)中常量引用的對(duì)象。
- 本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象
- 標(biāo)記-清除算法(Mark Sweep)
- 標(biāo)記-整理算法(Mark-Compact)
- 復(fù)制算法(Copying)
- 分代算法(Generational)
標(biāo)記一清除算法(Mark-Sweep)
算法分為“標(biāo)記”和“清除”兩個(gè)階段甫题,
首先標(biāo)記出所有需要回收的對(duì)象馁筐,然后回
收所有需要回收的對(duì)象缺點(diǎn)
效率問(wèn)題,標(biāo)記和清理兩個(gè)過(guò)程效率都不高
空間問(wèn)題坠非,
標(biāo)記清理之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片敏沉,空間碎片太多可能會(huì)導(dǎo)致后續(xù)使用中無(wú)法找到足夠的連續(xù)內(nèi)存而提前觸發(fā)另一次的垃圾搜集動(dòng)作效率不高,需要掃描所有對(duì)象炎码。堆越大盟迟,GC越慢
存在內(nèi)存碎片問(wèn)題。GC次數(shù)越多潦闲,碎片越為嚴(yán)重
復(fù)制(Copying) 搜集算法
將可用內(nèi)存劃分為兩塊攒菠,每次只使用其中的一塊,當(dāng)一半?yún)^(qū)內(nèi)存用完了歉闰,僅將還存活
的對(duì)象復(fù)制到另外一塊上面辖众,然后就把原來(lái)整塊內(nèi)存空間一次性清理掉,這樣使得每次內(nèi)存回收都是對(duì)整個(gè)半?yún)^(qū)的回收和敬,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況赵辕,只要移動(dòng)堆頂指針,按順序分配內(nèi)存就可以了概龄,實(shí)現(xiàn)簡(jiǎn)單还惠,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為原來(lái)的一半私杜,代價(jià)高昂
現(xiàn)在的商業(yè)虛擬機(jī)中都是用了這一種收集算法來(lái)回收新生代
將內(nèi)存分為一塊較大的eden空間和2塊較少的survivor空間蚕键,每次使用eden和其中一塊
survivor, 當(dāng)回收時(shí)將eden和survivor還存活的對(duì)象一次性拷 貝到另外一塊survivor空間上,然后清理掉eden和用過(guò)的survivorOracle Hotspot虛擬機(jī)默認(rèn)eden和survivor的大小比例是8:1衰粹,也就是每次只有10%的內(nèi)存是“浪費(fèi)”的
復(fù)制收集算法在對(duì)象存活率高的時(shí)候锣光,效率有所下降
如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保用于應(yīng)付半?yún)^(qū)內(nèi)存中所有對(duì)象都100%存活的極端情況铝耻,所以在老年代一般不能直接選用這種算法
- 只需要掃描存活的對(duì)象誊爹,效率更高
- 不會(huì)產(chǎn)生碎片
- 需要浪費(fèi)額外的內(nèi)存作為復(fù)制區(qū)
- 復(fù)制算法非常適合生命周期比較短的對(duì)象,因?yàn)槊看蜧C總能回收大部分的對(duì)象瓢捉,復(fù)制的開(kāi)銷(xiāo)比較小
- 根據(jù)IBM的專(zhuān)i研究频丘,98%的Java對(duì)象只會(huì)存活1個(gè)GC周期,對(duì)這些對(duì)象很適合用復(fù)制算法泡态。而且
不用1: 1的劃分工作區(qū)和復(fù)制區(qū)的空間
標(biāo)記一整理( Mark-Compact )算法
標(biāo)記過(guò)程仍然樣搂漠,但后續(xù)步驟不是進(jìn)行直接清理,而是令所有存活的對(duì)象一端移動(dòng)某弦,然后直接清理掉這端邊界以外的內(nèi)存桐汤。
沒(méi)有內(nèi)存碎片
比Mark-Sweep耗費(fèi)更多的時(shí)間進(jìn)行compact
分代收集而克。( GenerationalCollecting)算法
- 當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都是采用“分代收集”( Generational Collecting)算法,根據(jù)對(duì)象不同的存活周期將內(nèi)存劃分為幾塊怔毛。
- 一般是把Java堆分作新生代和老年代员萍,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ǎ┤缧律看蜧C都有大批對(duì)象死去拣度,只有少量存活碎绎,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本蜡娶,就可以完成收集。
Hotspot JVM 6中共劃分為三個(gè)代:
- 年輕代(Young Generation)
- 老年代(Old Generation)和
- 永久代( Permanent Generation)
年輕代(Young Generation)
新生成的對(duì)象都放在新生代映穗。年輕代用復(fù)制算法進(jìn)行GC (理論上年輕代對(duì)象的生命周期非常短窖张,所以適合復(fù)制算法)年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū)蚁滋,兩個(gè)Survivor區(qū)(可以通過(guò)參數(shù)設(shè)置Survivor個(gè)數(shù))宿接。對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí)辕录,還存活的對(duì)象將被復(fù)制到一個(gè)Survivor區(qū)睦霎,當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū)走诞,當(dāng)?shù)诙€(gè)Survivor區(qū)也滿了的時(shí)候副女,從第一個(gè)Survivor區(qū)復(fù)制過(guò)來(lái)的并且此時(shí)還存活的對(duì)象,將被復(fù)制到老年代蚣旱。2個(gè)Survivor是完全對(duì)稱(chēng)碑幅,輪流替換。
Eden和2個(gè)Survivor的缺省比例是8:1:1塞绿,也就是10%的空間會(huì)被
浪費(fèi)沟涨。可以根據(jù)GClog的信息調(diào)整大小的比例-
老年代(Old Generation)
- 存放了經(jīng)過(guò)一次或多次GC還存活的對(duì)象
- 一般采用Mark-Sweep或者M(jìn)ark-Compact算法進(jìn)行GC
- 有多種垃圾收集器可以選擇异吻。每種垃圾收集器可以看作一個(gè)GC算法的具體實(shí)現(xiàn)裹赴。可以根據(jù)具體應(yīng)用的需求選用合適的垃圾收集器(追求吞吐量?追求最短的響應(yīng)時(shí)間?)
-
永久代- 并不屬于堆(Heap).但是GC也會(huì)涉及到這個(gè)區(qū)域
- 存放了每個(gè)Class的結(jié)構(gòu)信息诀浪, 包括常量池棋返、字段描述、方法描述雷猪。與垃圾收集要收集的Java對(duì)象關(guān)系不大
內(nèi)存分配與回收
堆上分配
大多數(shù)情況在eden上分配懊昨,偶爾會(huì)直接在old上分配細(xì)節(jié)取決于GC的實(shí)現(xiàn)棧上分配
原子類(lèi)型的局部變量-
GC要做的是將那些dead的對(duì)象所占用的內(nèi)存回收掉
- Hotspot認(rèn)為沒(méi)有引用的對(duì)象是dead的
- Hotspot將引用分為四種: Strong、 Soft春宣、Weak酵颁、Phantom
Strong 即默認(rèn)通過(guò)Object o=new Object()這種方式賦值的引用
Soft嫉你、Weak、 Phantom這 三種則都是繼承Reference
-
在Full GC時(shí)會(huì)對(duì)Reference類(lèi)型的引用進(jìn)行特殊處理
- Soft:內(nèi)存不夠時(shí)一定會(huì)被GC躏惋、長(zhǎng)期不用也會(huì)被GC
- Weak: - 定會(huì)被GC幽污, 當(dāng)被mark為dead, 會(huì)在ReferenceQueue中通知
- Phantom: 本來(lái)就沒(méi)引用,當(dāng)從jvm heap中釋放時(shí)會(huì)通知
垃圾回收器
GC回收的時(shí)機(jī)
- 在分代模型的基礎(chǔ)上簿姨,GC從時(shí)機(jī)上分為兩種: Scavenge GC和Full GC
- Scavenge GC (Minor GC)
觸發(fā)時(shí)機(jī):新對(duì)象生成時(shí)距误,Eden空間滿了理論上Eden區(qū)大多數(shù)對(duì)象會(huì)在ScavengeGC回收,復(fù)制算法的執(zhí)
行效率會(huì)很高扁位,ScavengeGC時(shí)間比較短准潭。 - Full GC
對(duì)整個(gè)JVM進(jìn)行整理,包括Young域仇、Old 和Perm主要的觸發(fā)時(shí)機(jī): 1) Old滿了2) Perm滿了3) system.gc()效率很低刑然,盡量減少Full GC。
- Scavenge GC (Minor GC)
垃圾回收器(Garbage Collector)
- 分代模型: GC的宏觀愿景;
- 垃圾回收器: GC的具體實(shí)現(xiàn)
- Hotspot JVM提供多種垃圾回收器暇务,我們需要根據(jù)具體應(yīng)用的需要采用不同的回收器
- 沒(méi)有萬(wàn)能的垃圾回收器泼掠,每種垃圾回收器都有自己的適用場(chǎng)景
垃圾收集器的‘并行”和并發(fā)
- 并行(Parallel):指多個(gè)收集器的線程同時(shí)工作,但是用戶線程處于等待狀態(tài)
- 并發(fā)(Concurrent):指收集器在工作的同時(shí)垦细,可以允許用戶線程工作择镇。并發(fā)不代表解決了GC停頓的問(wèn)題,在關(guān)鍵的步驟還是要停頓括改。比如在收集器標(biāo)記垃圾的時(shí)候腻豌。但在清除垃圾的時(shí)候,用戶線程可以和GC線程并發(fā)執(zhí)行嘱能。
Serial收集器
- 最早的收集器饲梭,單線程進(jìn)行GC, New和Old Generation都可以使用焰檩,在新生代憔涉,采用復(fù)制算法;
- 在老年代,采用Mark-Compact算法因?yàn)槭菃尉€程GC析苫,沒(méi)有多線程切換的額外開(kāi)銷(xiāo)兜叨,簡(jiǎn)單實(shí)用
Hotspot Client模式默認(rèn)的收集器
ParNew收集器
ParNew收集器就是Serial的多線程版本,除了使用多個(gè)收集線程外衩侥,其余行為包括算法国旷、STW、對(duì)象分配規(guī)則茫死、回收策略等都與Seria收集器一模一樣跪但。
對(duì)應(yīng)的這種收集器是虛擬機(jī)運(yùn)行在Server模式的默認(rèn)新生代收集器,在單CPU的環(huán)境中峦萎,ParNew收集器并不會(huì)比Serial收集器有更好的效果
Serial收集器在新生代的多線程版本
使用復(fù)制算法(因?yàn)獒槍?duì)新生代)只有在多CPU的環(huán)境下屡久,效率才會(huì)比Serial收集器高
可以通過(guò)-XX:ParallelGC Threads來(lái)控制GC線程數(shù)的多少忆首。需要結(jié)合具體CPU的個(gè)數(shù)Server模式下新生代的缺省收集器
Parallel Scavenge收集器
- Parallel Scavenge收集器也是一個(gè)多線程收集器,也是使用復(fù)制算法被环,但它的對(duì)象分配規(guī)則與回收策略都與ParNew收集器有所不同糙及,它是以吞吐量最大化(即GC時(shí)間占總運(yùn)行時(shí)間最小)為目標(biāo)的收集器實(shí)現(xiàn),它允許較長(zhǎng)時(shí)間的STW換取總吞吐量最大化
CMS ( Concurrent Mark Sweep )收集器
CMS是一種以最短停頓時(shí)間為目標(biāo)的收集器筛欢,使用CMS并不能達(dá)到GC效率最高(總體GC時(shí)間最小)浸锨,但它能盡可能降低GC時(shí)服務(wù)的停頓時(shí)間,CMS收集器使用的是標(biāo)記一清除算法
-
特點(diǎn):
- 追求最短停頓時(shí)間版姑,非常適合Web應(yīng)用
- 只針對(duì)老年區(qū)柱搜,一般結(jié)合ParNew使用
- Concurrent, GC線程和用戶線程并發(fā)工作(盡量并發(fā) )
- Mark-Sweep
- 只有在多CPU環(huán)境下才有意義
- 使用-XX:+UseConcMarkSweepGC打開(kāi)
-
CMS收集器的缺點(diǎn)
- CMS以犧牲CPU資源的代價(jià)來(lái)減少用戶線程的停頓。當(dāng)CPU個(gè)數(shù)少于4的時(shí)候剥险,有可能對(duì)吞吐量影響非常大
- CMS在并發(fā)清理的過(guò)程中聪蘸,用戶線程還在跑。這時(shí)候需要預(yù)留一部分空間給用戶線程
- CMS用Mark-Sweep,會(huì)帶來(lái)碎片問(wèn)題炒嘲。碎片過(guò)多的時(shí)候會(huì)容易頻繁觸發(fā)FullGC
G1收集器
heap被劃分為一個(gè)個(gè)相等的不連續(xù)的內(nèi)存區(qū)域(regions) 宇姚,每個(gè)region都有一個(gè)分代的角色: eden匈庭、 survivor夫凸、 old
對(duì)每個(gè)角色的數(shù)量并沒(méi)有強(qiáng)制的限定,也就是說(shuō)對(duì)每種分代內(nèi)存的大小阱持,可以動(dòng)態(tài)變化
G1最大的特點(diǎn)就是高效的執(zhí)行回收夭拌,優(yōu)先去執(zhí)行那些大量對(duì)象可回收的區(qū)域(region)
G1使用了gc停頓可預(yù)測(cè)的模型,來(lái)滿足用戶設(shè)定的gc停頓時(shí)間衷咽,根據(jù)用戶設(shè)定的目標(biāo)時(shí)間鸽扁,G1會(huì)自動(dòng)地選擇哪些region要清除,次清除多少個(gè)region
G1從多個(gè)region中復(fù)制存活的對(duì)象镶骗,然后集中放入一個(gè)region中桶现,同時(shí)整理、清除內(nèi)存(copying收集算法)
對(duì)比使用mark-sweep的CMS, G1使用的copying算法不會(huì)造成內(nèi)存碎片;
對(duì)比Parallel Scavenge(基于copying )鼎姊、Parallel Old收集器(基于mark-compact-sweep)骡和,Parallel會(huì)對(duì)整個(gè)區(qū)域做整理導(dǎo)致gc停頓會(huì)比較長(zhǎng),而G1只是特定地整理幾個(gè)region相寇。
G1并非一個(gè)實(shí)時(shí)的收集器慰于,與parallelScavenge-樣,對(duì)gc停頓時(shí)間的設(shè)置并不絕對(duì)生效唤衫,只是G1有較高的幾率保證不超過(guò)設(shè)定的gc停頓時(shí)間婆赠。與之前的gc收集器對(duì)比,G1會(huì)根據(jù)用戶設(shè)定的gc停頓時(shí)間佳励,智能評(píng)估哪幾個(gè)region需要被回收可以滿足用戶的設(shè)定
分區(qū)(Region):
G1采取了不同的策略來(lái)解決并行休里、串行和CMS收集器的碎片蛆挫、暫停時(shí)間不可控等問(wèn)題一G1將 整個(gè)堆分成相同大小的分區(qū)(Region)
每個(gè)分區(qū)都可能是年輕代也可能是老年代,但是在同份帐,時(shí)刻只能屬于某個(gè)代璃吧。年輕代、幸存區(qū)废境、老年代這些概念還存在畜挨,成為邏輯上的概念,這樣方便復(fù)用之前分代框架的邏輯噩凹。
在物理巴元,上不需要連續(xù),則帶來(lái)了額外的好處有的分區(qū)內(nèi)垃圾對(duì)象特別多驮宴,有的分區(qū)內(nèi)垃圾對(duì)象很少逮刨,G1會(huì)優(yōu)先回收垃圾對(duì)象特別多的分區(qū),這樣可以花費(fèi)較少的時(shí)間來(lái)回收這些分區(qū)的垃圾堵泽,這也就是G1名字的由來(lái)修己,即首先收集垃圾最多的分區(qū)。
依然是在新生代滿了的時(shí)候迎罗,對(duì)整個(gè)新生代進(jìn)行回收整個(gè)新生代中的對(duì)象睬愤,要么被回收、要么晉升纹安,至于新生代也采取分區(qū)機(jī)制的原因尤辱,則是因?yàn)檫@樣跟老年代的策略統(tǒng)一,方便調(diào)整代的大小
G1還是一種帶壓縮的收集器厢岂,在回收老年代的分區(qū)時(shí)光督,是將存活的對(duì)象從一個(gè)分區(qū)拷貝到另一個(gè)可用分區(qū),這個(gè)拷貝的過(guò)程就實(shí)現(xiàn)了局部的壓縮塔粒。
收集集合(CSet)
- 一組可被回收的分區(qū)的集合结借。在CSet中存活的數(shù)據(jù)會(huì)在GC過(guò)程中被移動(dòng)到另一個(gè)可用分區(qū),CSet中的分區(qū)可以來(lái)自eden空間卒茬、survivor空間船老、 或者老年代
已記憶集合(RSet) :
RSet記錄了其他Region中的對(duì)象引用本Region中對(duì)象的關(guān)系,屬于points-into結(jié)構(gòu)( 誰(shuí)引用了我的對(duì)象)RSet的價(jià)值在于使得垃圾收集器不需要掃描整個(gè)堆找到誰(shuí)引用了當(dāng)前分區(qū)中的對(duì)象扬虚,只需要掃描RSet即可努隙。
Region1和Region3中的對(duì)象都引用了Region2中的對(duì)象,因此在Region2的RSet中記錄了這兩個(gè)引用辜昵。
G1 GC是在points-out的card table之上再加了一層結(jié)構(gòu)來(lái)構(gòu)成points-into RSet:每個(gè)region會(huì)記錄下到底哪些別的
region有指向自己的指針荸镊,而這些指針?lè)謩e在哪些card的范圍內(nèi)。這個(gè)RSet其實(shí)是一個(gè)hash table,key是別的region的起始地址,value是一個(gè)集合躬存,里面的元素是card table的index.
舉例來(lái)說(shuō)张惹,如果region A的RSet里有一項(xiàng)的key是region B,value里有index為1234的card,它的意思就是region B的
一個(gè)card里 有引用指向region A岭洲。所以對(duì)region A來(lái)說(shuō)宛逗,該RSet記錄的是points-into的關(guān)系;而card table仍然記錄了points-out的關(guān)系。Snapshot-AtThe-Beginning(SATB):SATB是G1 GC在并發(fā)標(biāo)記階段使用的增量式的標(biāo)記算法盾剩,
并發(fā)標(biāo)記是并發(fā)多線程的雷激,但并發(fā)線程在同一時(shí)刻只掃描一個(gè)分區(qū)
參考鏈接:https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
G1相對(duì)于CMS的優(yōu)勢(shì)
- G1在壓縮空間方面有優(yōu)勢(shì)
- G1通過(guò)將內(nèi)存空間分成區(qū)域(Region) 的方式避免內(nèi)存碎片問(wèn)題Eden、Survivor告私、 Old區(qū)不再固定屎暇,在內(nèi)存使用效率上來(lái)說(shuō)更靈活
- G1可以通過(guò)設(shè)置預(yù)期停頓時(shí)間( Pause Time) 來(lái)控制垃圾收集時(shí)間,避免應(yīng)用雪崩現(xiàn)象
- G1在回收內(nèi)存后會(huì)馬上同時(shí)做合并空閑內(nèi)存的工作驻粟,而CMS默認(rèn)是在STW ( stop the world) 的時(shí)候做
- G1會(huì)在Young GC中使用根悼,而CMS只能在Old區(qū)使用
G1的適合場(chǎng)景
- 服務(wù)端多核CPU、JVM內(nèi)存占用較大的應(yīng)用
- 應(yīng)用在運(yùn)行過(guò)程中會(huì)產(chǎn)生大量?jī)?nèi)存碎片蜀撑、需要經(jīng)常壓縮空間
- 想要更可控挤巡、可預(yù)期的GC停頓周期:防止高并發(fā)下應(yīng)用的雪崩現(xiàn)象
G1 GC模式
G1提供了兩種GC模式,Young GC和Mixed GC, 兩種都是完全Stop The World的
Young GC:選定所有年輕代里的Region酷麦。通過(guò)控制年輕代的Region個(gè)數(shù)矿卑,即年輕代內(nèi)存大小,來(lái)控制Young GC的時(shí)間開(kāi)銷(xiāo)贴铜。
Mixed GC:選定所有年輕代里的Region,外加根據(jù)global concurrent marking統(tǒng)計(jì)得出收集收益高的若干老年代Region粪摘。在用戶指定的開(kāi)銷(xiāo)目標(biāo)范圍內(nèi)盡可能選擇收益高的老年代Region
Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC實(shí)在無(wú)法跟上程序分配內(nèi)存的速度瀑晒,導(dǎo)致老年代填滿無(wú)法繼續(xù)進(jìn)行MixedGC绍坝,就會(huì)使用serialold GC (Full GC)來(lái)收集整個(gè)GC heap。所以本質(zhì)上苔悦,G1是不提供Full GC的
global concurrent marking
初始標(biāo)記( initial mark, STW) :它標(biāo)記了從GCRoot開(kāi)始直接可達(dá)的對(duì)象轩褐。
并發(fā)標(biāo)記( Concurrent Marking) :這個(gè)階段從GC Root開(kāi)始對(duì)heap中的對(duì)象進(jìn)行標(biāo)記,標(biāo)記線
程與應(yīng)用程序線程并發(fā)執(zhí)行玖详,并且收集各個(gè)Region的存活對(duì)象信息把介。重新標(biāo)記( Remark, STW) :標(biāo)記那些在并發(fā)標(biāo)記階段發(fā)生變化的對(duì)象,將被回收蟋座。
清理(Cleanup) :清除空Region (沒(méi)有存活對(duì)象的)拗踢,加入到free list。
第一階段initial mark是共用了Young GC的暫停向臀,這是因?yàn)樗麄兛梢詮?fù)用rootscan操作巢墅,所以可以說(shuō)global concurrent marking是伴隨Young GC而發(fā)生的
第四階段Cleanup只是回收了沒(méi)有存活對(duì)象的Region,所以它并不需要STW。
G1在運(yùn)行過(guò)程中的主要模式
- YGC(不同于CMS)
- G1 YGC在Eden充滿時(shí)觸發(fā)君纫,在回收之后所有之前屬于Eden的區(qū)塊全部變成空白驯遇,即不屬于任何一個(gè)分區(qū)( Eden、Survivor蓄髓、Old )
-
YGC執(zhí)行步驟:
- 階段1:根掃描
靜態(tài)和本地對(duì)象被描 - 階段2:更新RS
處理dirty card隊(duì)列更新RS - 階段3:處理RS
檢測(cè)從年輕代指向老年代的對(duì)象 - 階段4:對(duì)象拷貝
拷貝存活的對(duì)象到survivor/old區(qū)域 - 階段5:處理引用隊(duì)列
軟引用叉庐,弱引用,虛引用處理
- 階段1:根掃描
- 并發(fā)階段(global concurrent marking)
- 混合模式
- Full GC (一 般是G1出現(xiàn)問(wèn)題時(shí)發(fā)生会喝,本質(zhì)上不屬于G1陡叠,G1進(jìn)行的回退策略(回退為:Serial Old GC))
什么時(shí)候發(fā)生MixedGC?
- 由一些參數(shù)控制,另外也控制著哪些老年代Region會(huì)被選入CSet (收集集合)
- G1HeapWastePercent:在globalconcurrent marking結(jié)束之后肢执,我們可以知道oldgenregions中有多少空間要被回收匾竿,在每次YGC之后和再次發(fā)生MixedGC之前,會(huì)檢查垃圾占比是否達(dá)到此參數(shù)蔚万,只有達(dá)到了岭妖,下次才 會(huì)發(fā)生Mixed GC
- G1MixedGCLiveThresholdPercent: oldgeneration region中的存活對(duì)象的占比,只有在此參數(shù)之下反璃,才會(huì)被選入CSet
- G1MixedGCCountTarget:一 次globalconcurrent marking之后昵慌,最多執(zhí)行Mixed GC的次數(shù)
- G1OldCSetRegionThresholdPercent:次Mixed GC中能被選入CSet的最多old generation region數(shù)量
三色標(biāo)記算法
提到并發(fā)標(biāo)記,我們不得不了解并發(fā)標(biāo)記的三色標(biāo)記算法淮蜈。它是描述追蹤式回收器的一種有效的方法斋攀,利用它可以推演回收器的正確性
- 我們將對(duì)象分成三種類(lèi)型:
- 黑色:根對(duì)象,或者該對(duì)象與它的子對(duì)象都被掃描過(guò)(對(duì)象被標(biāo)記了梧田,且它的所有field也被標(biāo)記完了)
- 灰色:對(duì)象本身被掃描,但還沒(méi)掃描完該對(duì)象中的子對(duì)象( 它的field還沒(méi)有被標(biāo)記或標(biāo)記完)
- 白色:未被掃描對(duì)象淳蔼,掃描完成所有對(duì)象之后,最終為白色的為不可達(dá)對(duì)象裁眯,即垃圾對(duì)象(對(duì)象沒(méi)有被標(biāo)記到)
提到并發(fā)標(biāo)記鹉梨,我們不得不了解并發(fā)標(biāo)記的三色標(biāo)記算法。它是描述追蹤式回收器的一種有效的方法穿稳,利用它可以推演回收器的正確性
遍歷了所有可達(dá)的對(duì)象后存皂,所有可達(dá)的對(duì)象都變成了黑色。不可達(dá)的對(duì)象即為白色逢艘,需要被清理,如圖:
- 但是如果在標(biāo)記過(guò)程中旦袋,應(yīng)用程序也在運(yùn)行,那么對(duì)象的指針就有可能改變它改。這樣的話疤孕,我們就會(huì)遇到一個(gè)問(wèn)題:對(duì)象丟失問(wèn)題
這時(shí)候應(yīng)用程序執(zhí)行了以下操作:
A.c=C
B.c=null
這樣,對(duì)象的狀態(tài)圖變成如下情形:
這時(shí)候垃圾收集器再標(biāo)記掃描的時(shí)候就會(huì)變成下圖這樣
- 很顯然央拖,此時(shí)C是白色祭阀,被認(rèn)為是垃圾需要清理掉截亦,顯然這是不合理的
SATB
- 在G1中,使用的是SATB ( Snapshot-At-The- Beginning)的方式柬讨,刪除的時(shí)候記錄所有的對(duì)象
- 它有3個(gè)步驟
- 在開(kāi)始標(biāo)記的時(shí)候生成一個(gè)快照?qǐng)D崩瓤,標(biāo)記存活對(duì)象
- 在并發(fā)標(biāo)記的時(shí)候所有被改變的對(duì)象入隊(duì)(在writebarrier里把所有舊的引用所指向的對(duì)象都變成非白的)
- 可能存在浮動(dòng)垃圾,將在下次被收集
G1混合式回收
- G1到現(xiàn)在可以知道哪些老的分區(qū)可回收垃圾最多踩官。當(dāng)全局并發(fā)標(biāo)記完成后却桶,在某個(gè)時(shí)刻,就開(kāi)始了Mixed GC蔗牡。這些垃圾回收被稱(chēng)作“混合式”是因?yàn)樗麄儾粌H僅進(jìn)行正常的新生代垃圾收集颖系,同時(shí)也回收部分后臺(tái)掃描線程標(biāo)記的分區(qū)混合式GC也是采用的復(fù)制清理策略,當(dāng)GC完成后辩越,會(huì)重新釋放空間
SATB詳解
- SATB是維持并發(fā)GC的一種手段嘁扼。G1并發(fā)的基礎(chǔ)就是SATB。SATB可以理解成在GC開(kāi)始之前對(duì)堆內(nèi)存里的對(duì)象做次快照黔攒,此時(shí)活的對(duì)象就認(rèn)為是活的趁啸,從而形成了一個(gè)對(duì)象圖。
- 在GC收集的時(shí)候督惰,新生代的對(duì)象也認(rèn)為是活的對(duì)象不傅,除此之外其他不可達(dá)的對(duì)象都認(rèn)為是垃圾對(duì)象
如何找到在GC過(guò)程中分配的對(duì)象呢?
每個(gè)region記錄著兩個(gè)top-at-mark-start ( TAMS 指針,分別為prevTAMS和nextTAMS赏胚。在TAMS以上的對(duì)象就是新分配的访娶,因而被視為隱式marked。
通過(guò)這種方式我們就找到了在GC過(guò)程中新分配的對(duì)象觉阅,并把這些對(duì)象認(rèn)為是活的對(duì)象崖疤。
解決了對(duì)象在GC過(guò)程中分配的問(wèn)題,那么在GC過(guò)程中引用發(fā)生變化的問(wèn)題怎么解決呢?
G1給出的解決辦法是通過(guò)WriteBarrier.Write Barrier就是對(duì)引用字段進(jìn)行賦值做了額外處理典勇。通過(guò)Write Barrier就可以了解到哪些引用對(duì)象發(fā)生了什么樣的變化
mark的過(guò)程就是遍歷heap標(biāo)記live object的過(guò)程劫哼,
采用的是三色標(biāo)記算法,這三種顏色為white(表示還未訪問(wèn)到)痴柔、gray(訪問(wèn)到但是它用到的引用還沒(méi)有完全掃描沦偎、black( 訪問(wèn)到而且其用到的引用已經(jīng)完全掃描完)
整個(gè)三色標(biāo)記算法就是從GCroots出發(fā)遍歷heap,針對(duì)可達(dá)對(duì)象先標(biāo)記white為gray,然后再標(biāo)記gray為black;遍歷完成之后所有可達(dá)對(duì)象都是black的疫向,所有white都是可以回收的
SATB僅僅對(duì)于在marking開(kāi)始階段進(jìn)行"snapshot"(marked all reachable at markstart)咳蔚,但是concurrent的時(shí)候并發(fā)修改可能造成對(duì)象漏標(biāo)記
對(duì)black新引用了一個(gè)white對(duì)象,然后又從gray對(duì)象中刪除了對(duì)該white對(duì)象的引用搔驼,這樣會(huì)造成了該white對(duì)象漏標(biāo)記
對(duì)black新引用了一個(gè)white對(duì)象谈火,然后從gray對(duì)象刪了一個(gè)引用該white對(duì)象的white對(duì)象,這樣也會(huì)造成了該white對(duì)象漏標(biāo)記舌涨,
對(duì)black新引用了一個(gè)剛new出來(lái)的white對(duì)象,沒(méi)有其他gray對(duì)象引用該white對(duì)象,這樣也會(huì)造成了該white對(duì)象漏標(biāo)記
對(duì)于三色算法在concurrent的時(shí)候可能產(chǎn)生的漏標(biāo)記問(wèn)題革为,SATB在marking階段中舵鳞,對(duì)于從gray對(duì)象移除的目標(biāo)引用對(duì)象標(biāo)記為gray,對(duì)于black引用的新產(chǎn)生的對(duì)象標(biāo)記為black;由于是在開(kāi)始的時(shí)候進(jìn)行snapshot,因而可能存在Floating Garbage
漏標(biāo)與誤標(biāo)
誤標(biāo)沒(méi)什么關(guān)系震檩,頂多造成浮動(dòng)垃圾,在下次gc還是可以回收的蜓堕,但是漏標(biāo)的后果是致命的抛虏,把本應(yīng)該存活的對(duì)象給回收了,從而影響的程序的正確性
-
漏標(biāo)的情況只會(huì)發(fā)生在白色對(duì)象中套才,且滿足以下任意一個(gè)條件
并發(fā)標(biāo)記時(shí)迂猴,應(yīng)用線程給一個(gè)黑色對(duì)象的引用類(lèi)型字段賦值 了該白色對(duì)象
并發(fā)標(biāo)記時(shí),應(yīng)用線程刪除所有灰色對(duì)象到該白色對(duì)象的引用
對(duì)于第一種情況背伴,利用post-write barrier,記錄所有新增的引用關(guān)系沸毁,然后根據(jù)這些引用關(guān)系為根重新掃描一-遍
對(duì)于第二種情況,利用pre-write barrier,將所有即將被刪除的引用關(guān)系的舊引用記錄下來(lái)傻寂,最后以這些舊引用為根重新掃描一遍
停頓預(yù)測(cè)模型
G1收集器突出表現(xiàn)出來(lái)的一點(diǎn)是通過(guò)一個(gè)停頓預(yù)測(cè)模型根據(jù)用戶配置的停頓時(shí)間來(lái)選擇CSet的大小以清,從而達(dá)到用戶期待的應(yīng)用程序暫停時(shí)間。
通過(guò)-XX:MaxGCPauseMillis參數(shù)來(lái)設(shè)置崎逃。這一點(diǎn)有點(diǎn)類(lèi)似于ParallelScavenge收集器掷倔。 關(guān)于停頓時(shí)間的設(shè)置并不是越短越好。
設(shè)置的時(shí)間越短意味著每次收集的CSet越小个绍,導(dǎo)致垃圾逐步積累變多勒葱,最終不得不退化成SerialGC;停頓時(shí)間設(shè)置的過(guò)長(zhǎng),那么會(huì)導(dǎo)致每次都會(huì)產(chǎn)生長(zhǎng)時(shí)間的停頓巴柿,影響了程序?qū)ν獾捻憫?yīng)時(shí)間
G1的收集模式
- G1的運(yùn)行過(guò)程是這樣的:會(huì)在Young GC和Mixed GC之間不斷地切換運(yùn)行凛虽,同時(shí)定期地做全局并發(fā)標(biāo)記,在實(shí)在趕不上對(duì)象創(chuàng)建速度的情況下 使用Full GC(Serial GC)广恢。
- 初始標(biāo)記是在Young GC.上執(zhí)行的凯旋,在進(jìn)行全局并發(fā)標(biāo)記的時(shí)候不會(huì)做MixedGC,在做MixedGC的時(shí)候也不會(huì)啟動(dòng)初始標(biāo)記階段。
- 當(dāng)MixedGC趕不上對(duì)象產(chǎn)生的速度的時(shí)候就退化成FullGC钉迷,這一點(diǎn)是需要重點(diǎn)調(diào)優(yōu)的地方
G1最佳實(shí)踐
不要設(shè)置新生代和老年代的大小,G1收集器在運(yùn)行的時(shí)候會(huì)調(diào)整新生代和老年代
的大小荒椭。通過(guò)改變代的大小來(lái)調(diào)整對(duì)象晉升的速度以及晉升年齡趣惠,從而達(dá)到我們?yōu)槭占髟O(shè)置的暫停時(shí)間目標(biāo)草戈。設(shè)置了新生代大小相當(dāng)于放棄了G1為我們做的自動(dòng)調(diào)優(yōu)猾瘸。我們需要做的只是設(shè)置整個(gè)堆內(nèi)存的大小牵触,剩下的交給G1自已去分配各個(gè)代的大小即可揽思。
-
不斷調(diào)優(yōu)暫停時(shí)間指標(biāo)
- 通過(guò)-XX:MaxGCPauseMillis=x可以設(shè)置啟動(dòng)應(yīng)用程序暫停的時(shí)間,G1在運(yùn)行的時(shí)候會(huì)根據(jù)這個(gè)參數(shù)選擇CSet來(lái)滿足響應(yīng)時(shí)間的設(shè)置损痰。一般情況下這個(gè)值設(shè)置到100ms或者200ms都是可以的(不同情況下會(huì)不一樣)卢未,但如果設(shè)置成50ms就不太合理辽社。暫停時(shí)間設(shè)置的太短,就會(huì)導(dǎo)致出 現(xiàn)G1跟不上垃圾產(chǎn)生的速度汉匙。最終退化成Full GC噩翠。所以對(duì)這個(gè)參數(shù)的調(diào)優(yōu)是一個(gè)持續(xù)的過(guò)程绎秒,逐步調(diào)整到最佳狀態(tài)见芹。
-
關(guān)注Evacuation Failure
- Evacuation(表示copy) Failure類(lèi)似于CMS里面的晉升失敗,堆空間的垃圾太多導(dǎo)致無(wú)法完成Region之間的拷貝徘铝,于是不得不退化成Full GC來(lái)做一次全局范圍內(nèi)的垃圾收集
G1日志解析:
/**
*
* G1日志分析
* 虛擬機(jī)相關(guān)參數(shù):
* -verbose:gc
* -Xms10m
* -Xmx10m
* -XX:+UseG1GC 表示垃圾收集器使用G1
* -XX:+PrintGCDetails
* -XX:+PrintGCDateStamps
* -XX:MaxGCPauseMillis=200m 設(shè)置垃圾收集最大停頓時(shí)間
*/
public class G1LogAnalysis {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] bytes1 = new byte[size];
byte[] bytes2 = new byte[size];
byte[] bytes3 = new byte[size];
byte[] bytes4 = new byte[size];
System.out.println("hello world");
}
}
/**
* GC日志:
* 2020-04-14T16:13:41.663+0800: [GC pause (G1 Humongous Allocation【說(shuō)明分配的對(duì)象超過(guò)了region大小的50%】) (young) (initial-mark), 0.0014516 secs]
* [Parallel Time: 1.1 ms, GC Workers: 4【GC工作線程數(shù)】]
* [GC Worker Start (ms): Min: 167.0, Avg: 167.1, Max: 167.1, Diff: 0.1]【幾個(gè)垃圾收集工作的相關(guān)信息統(tǒng)計(jì)】
* [Ext Root Scanning (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 1.6]
* [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
* [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
* [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
* [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
* [Object Copy (ms): Min: 0.6, Avg: 0.6, Max: 0.6, Diff: 0.0, Sum: 2.4]
* [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
* [Termination Attempts: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 5]
* 【上面的幾個(gè)步驟為YOUNG GC的固定執(zhí)行步驟】
* 階段1:根掃描
* 靜態(tài)和本地對(duì)象被描
* 階段2:更新RS
* 處理dirty card隊(duì)列更新RS
* 階段3:處理RS
* 檢測(cè)從年輕代指向老年代的對(duì)象
* 階段4:對(duì)象拷貝
* 拷貝存活的對(duì)象到survivor/old區(qū)域
* 階段5:處理引用隊(duì)列
* 軟引用,弱引用淹魄,虛引用處理
* [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
* [GC Worker Total (ms): Min: 1.0, Avg: 1.1, Max: 1.1, Diff: 0.1, Sum: 4.2]
* [GC Worker End (ms): Min: 168.1, Avg: 168.1, Max: 168.1, Diff: 0.0]
* [Code Root Fixup: 0.0 ms]
* [Code Root Purge: 0.0 ms]
* [Clear CT: 0.1 ms]【清楚cardTable所花費(fèi)時(shí)間】
* [Other: 0.3 ms]
* [Choose CSet: 0.0 ms]
* [Ref Proc: 0.1 ms]
* [Ref Enq: 0.0 ms]
* [Redirty Cards: 0.1 ms]
* [Humongous Register: 0.0 ms]
* [Humongous Reclaim: 0.0 ms]
* [Free CSet: 0.0 ms]
* [Eden: 2048.0K(4096.0K)->0.0B【新生代清理后】(2048.0K) Survivors: 0.0B->1024.0K Heap: 3800.2K(10.0M)->2752.1K(10.0M)]
* [Times: user=0.00 sys=0.00, real=0.01 secs]
* 2020-04-14T16:13:41.671+0800: [GC concurrent-root-region-scan-start]
* 2020-04-14T16:13:41.671+0800: [GC concurrent-root-region-scan-end, 0.0008592 secs]
* 2020-04-14T16:13:41.671+0800: [GC concurrent-mark-start]
* 2020-04-14T16:13:41.672+0800: [GC concurrent-mark-end, 0.0000795 secs]
* 2020-04-14T16:13:41.672+0800: [GC remark 2020-04-14T16:13:41.672+0800: [Finalize Marking, 0.0001170 secs] 2020-04-14T16:13:41.672+0800: [GC ref-proc, 0.0002159 secs] 2020-04-14T16:13:41.672+0800: [Unloading, 0.0005800 secs], 0.0011024 secs]
* [Times: user=0.00 sys=0.00, real=0.00 secs]
* 2020-04-14T16:13:41.673+0800: [GC cleanup 4800K->4800K(10M), 0.0003239 secs]
* [Times: user=0.00 sys=0.00, real=0.00 secs]
* hello world
* Heap
* garbage-first heap total 10240K, used 4800K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
* region size 1024K【說(shuō)明region默認(rèn)大小】, 2 young (2048K), 1 survivors (1024K)
* Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K
* class space used 350K, capacity 388K, committed 512K, reserved 1048576K
*/