我們已經(jīng)知道Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,所有對(duì)象實(shí)例和數(shù)組都在堆上進(jìn)行內(nèi)存分配。為了進(jìn)行高效的垃圾回收,虛擬機(jī)把堆內(nèi)存劃分成新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)3個(gè)區(qū)域。

新生代
新生代由 Eden 與 Survivor Space(S0,S1)構(gòu)成,大小通過(guò)-Xmn參數(shù)指定,Eden 與 Survivor Space 的內(nèi)存大小比例默認(rèn)為8:1,可以通過(guò)-XX:SurvivorRatio 參數(shù)指定,比如新生代為10M 時(shí),Eden分配8M,S0和S1各分配1M。
Eden:希臘語(yǔ),意思為伊甸園,在圣經(jīng)中,伊甸園含有樂(lè)園的意思,根據(jù)《舊約·創(chuàng)世紀(jì)》記載,上帝耶和華照自己的形像造了第一個(gè)男人亞當(dāng),再用亞當(dāng)?shù)囊粋€(gè)肋骨創(chuàng)造了一個(gè)女人夏娃,并安置他們住在了伊甸園。
大多數(shù)情況下,對(duì)象在Eden中分配,當(dāng)Eden沒(méi)有足夠空間時(shí),會(huì)觸發(fā)一次Minor GC,虛擬機(jī)提供了-XX:+PrintGCDetails參數(shù),告訴虛擬機(jī)在發(fā)生垃圾回收時(shí)打印內(nèi)存回收日志。
Survivor:意思為幸存者,是新生代和老年代的緩沖區(qū)域。
當(dāng)新生代發(fā)生GC(Minor GC)時(shí),會(huì)將存活的對(duì)象移動(dòng)到S0內(nèi)存區(qū)域,并清空Eden區(qū)域,當(dāng)再次發(fā)生Minor GC時(shí),將Eden和S0中存活的對(duì)象移動(dòng)到S1內(nèi)存區(qū)域。
存活對(duì)象會(huì)反復(fù)在S0和S1之間移動(dòng),當(dāng)對(duì)象從Eden移動(dòng)到Survivor或者在Survivor之間移動(dòng)時(shí),對(duì)象的GC年齡自動(dòng)累加,當(dāng)GC年齡超過(guò)默認(rèn)閾值15時(shí),會(huì)將該對(duì)象移動(dòng)到老年代,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold 對(duì)GC年齡的閾值進(jìn)行設(shè)置。
老年代
老年代的空間大小即-Xmx 與-Xmn 兩個(gè)參數(shù)之差,用于存放經(jīng)過(guò)幾次Minor GC之后依舊存活的對(duì)象。當(dāng)老年代的空間不足時(shí),會(huì)觸發(fā)Major GC/Full GC,速度一般比Minor GC慢10倍以上。
永久代
在JDK8之前的HotSpot實(shí)現(xiàn)中,類(lèi)的元數(shù)據(jù)如方法數(shù)據(jù)、方法信息(字節(jié)碼,棧和變量大小)、運(yùn)行時(shí)常量池、已確定的符號(hào)引用和虛方法表等被保存在永久代中,32位默認(rèn)永久代的大小為64M,64位默認(rèn)為85M,可以通過(guò)參數(shù)-XX:MaxPermSize進(jìn)行設(shè)置,一旦類(lèi)的元數(shù)據(jù)超過(guò)了永久代大小,就會(huì)拋出OOM異常。
虛擬機(jī)團(tuán)隊(duì)在JDK8的HotSpot中,把永久代從Java堆中移除了,并把類(lèi)的元數(shù)據(jù)直接保存在本地內(nèi)存區(qū)域(堆外內(nèi)存),稱(chēng)之為元空間。
這樣做有什么好處?
有經(jīng)驗(yàn)的同學(xué)會(huì)發(fā)現(xiàn),對(duì)永久代的調(diào)優(yōu)過(guò)程非常困難,永久代的大小很難確定,其中涉及到太多因素,如類(lèi)的總數(shù)、常量池大小和方法數(shù)量等,而且永久代的數(shù)據(jù)可能會(huì)隨著每一次Full GC而發(fā)生移動(dòng)。
而在JDK8中,類(lèi)的元數(shù)據(jù)保存在本地內(nèi)存中,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間,可以避免永久代的內(nèi)存溢出問(wèn)題,不過(guò)需要監(jiān)控內(nèi)存的消耗情況,一旦發(fā)生內(nèi)存泄漏,會(huì)占用大量的本地內(nèi)存。
ps:JDK7之前的HotSpot,字符串常量池的字符串被存儲(chǔ)在永久代中,因此可能導(dǎo)致一系列的性能問(wèn)題和內(nèi)存溢出錯(cuò)誤。在JDK8中,字符串常量池中只保存字符串的引用。
如何判斷對(duì)象是否存活
GC動(dòng)作發(fā)生之前,需要確定堆內(nèi)存中哪些對(duì)象是存活的,一般有兩種方法:引用計(jì)數(shù)法和可達(dá)性分析法。
1、引用計(jì)數(shù)法
在對(duì)象上添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)對(duì)象引用它時(shí),計(jì)數(shù)器加1,當(dāng)使用完該對(duì)象時(shí),計(jì)數(shù)器減1,計(jì)數(shù)器值為0的對(duì)象表示不可能再被使用。
引用計(jì)數(shù)法實(shí)現(xiàn)簡(jiǎn)單,判定高效,但不能解決對(duì)象之間相互引用的問(wèn)題。
<pre class="brush: java; gutter: true; first-line: 1 hljs" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; font-style: normal; font-variant: normal; font-weight: 400; font-stretch: normal; font-size: 12px; line-height: 20px; font-family: "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class GCtest {
private Object instance = null;
private static final int _10M = 10 * 1 << 20;
// 一個(gè)對(duì)象占10M,方便在GC日志中看出是否被回收
private byte[] bigSize = new byte[_10M];
public static void main(String[] args) {
GCtest objA = new GCtest();
GCtest objB = new GCtest();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}</pre>
通過(guò)添加-XX:+PrintGC參數(shù),運(yùn)行結(jié)果:
<pre class="brush: java; gutter: true; first-line: 1 hljs json" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; font-style: normal; font-variant: normal; font-weight: 400; font-stretch: normal; font-size: 12px; line-height: 20px; font-family: "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]</pre>
從GC日志中可以看出objA和objB雖然相互引用,但是它們所占的內(nèi)存還是被垃圾收集器回收了。
2、可達(dá)性分析法
通過(guò)一系列稱(chēng)為 “GC Roots” 的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索路徑稱(chēng)為 “引用鏈”,以下對(duì)象可作為GC Roots:
- 本地變量表中引用的對(duì)象
- 方法區(qū)中靜態(tài)變量引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- Native方法引用的對(duì)象
當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈時(shí),意味著該對(duì)象可以被回收。

在可達(dá)性分析法中,判定一個(gè)對(duì)象objA是否可回收,至少要經(jīng)歷兩次標(biāo)記過(guò)程:
1、如果對(duì)象objA到 GC Roots沒(méi)有引用鏈,則進(jìn)行第一次標(biāo)記。
2、如果對(duì)象objA重寫(xiě)了finalize()方法,且還未執(zhí)行過(guò),那么objA會(huì)被插入到F-Queue隊(duì)列中,由一個(gè)虛擬機(jī)自動(dòng)創(chuàng)建的、低優(yōu)先級(jí)的Finalizer線程觸發(fā)其finalize()方法。finalize()方法是對(duì)象逃脫死亡的最后機(jī)會(huì),GC會(huì)對(duì)隊(duì)列中的對(duì)象進(jìn)行第二次標(biāo)記,如果objA在finalize()方法中與引用鏈上的任何一個(gè)對(duì)象建立聯(lián)系,那么在第二次標(biāo)記時(shí),objA會(huì)被移出“即將回收”集合。
看看具體實(shí)現(xiàn)
<pre class="brush: java; gutter: true; first-line: 1 hljs cs" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; font-style: normal; font-variant: normal; font-weight: 400; font-stretch: normal; font-size: 12px; line-height: 20px; font-family: "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class FinalizerTest {
public static FinalizerTest object;
public void isAlive() {
System.out.println("I'm alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("method finalize is running");
object = this;
}
public static void main(String[] args) throws Exception {
object = new FinalizerTest();
// 第一次執(zhí)行,finalize方法會(huì)自救
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
// 第二次執(zhí)行,finalize方法已經(jīng)執(zhí)行過(guò)
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
}
}</pre>
執(zhí)行結(jié)果:
<pre class="brush: java; gutter: true; first-line: 1 hljs coffeescript" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; font-style: normal; font-variant: normal; font-weight: 400; font-stretch: normal; font-size: 12px; line-height: 20px; font-family: "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">method finalize is running
I'm alive
I'm dead</pre>
從執(zhí)行結(jié)果可以看出:
第一次發(fā)生GC時(shí),finalize方法的確執(zhí)行了,并且在被回收之前成功逃脫;
第二次發(fā)生GC時(shí),由于finalize方法只會(huì)被JVM調(diào)用一次,object被回收。
當(dāng)然了,在實(shí)際項(xiàng)目中應(yīng)該盡量避免使用finalize方法。
收集算法
垃圾收集算法主要有:標(biāo)記-清除、復(fù)制和標(biāo)記-整理。
1、標(biāo)記-清除算法
對(duì)待回收的對(duì)象進(jìn)行標(biāo)記。
算法缺點(diǎn):效率問(wèn)題,標(biāo)記和清除過(guò)程效率都很低;空間問(wèn)題,收集之后會(huì)產(chǎn)生大量的內(nèi)存碎片,不利于大對(duì)象的分配。
2、復(fù)制算法
復(fù)制算法將可用內(nèi)存劃分成大小相等的兩塊A和B,每次只使用其中一塊,當(dāng)A的內(nèi)存用完了,就把存活的對(duì)象復(fù)制到B,并清空A的內(nèi)存,不僅提高了標(biāo)記的效率,因?yàn)橹恍枰獦?biāo)記存活的對(duì)象,同時(shí)也避免了內(nèi)存碎片的問(wèn)題,代價(jià)是可用內(nèi)存縮小為原來(lái)的一半。
3、標(biāo)記-整理算法
在老年代中,對(duì)象存活率較高,復(fù)制算法的效率很低。在標(biāo)記-整理算法中,標(biāo)記出所有存活的對(duì)象,并移動(dòng)到一端,然后直接清理邊界以外的內(nèi)存。
對(duì)象標(biāo)記過(guò)程
在可達(dá)性分析過(guò)程中,為了準(zhǔn)確找出與GC Roots相關(guān)聯(lián)的對(duì)象,必須要求整個(gè)執(zhí)行引擎看起來(lái)像是被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,即暫停所有運(yùn)行中的線程,不可以出現(xiàn)對(duì)象的引用關(guān)系還在不斷變化的情況。
如何快速枚舉GC Roots?
GC Roots主要在全局性的引用(常量或類(lèi)靜態(tài)屬性)與執(zhí)行上下文(本地變量表中的引用)中,很多應(yīng)用僅僅方法區(qū)就上百兆,如果進(jìn)行遍歷查找,效率會(huì)非常低下。
在HotSpot中,使用一組稱(chēng)為OopMap的數(shù)據(jù)結(jié)構(gòu)進(jìn)行實(shí)現(xiàn)。類(lèi)加載完成時(shí),HotSpot把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái)存儲(chǔ)到OopMap中,通過(guò)JIT編譯出來(lái)的本地代碼,也會(huì)記錄下棧和寄存器中哪些位置是引用。GC發(fā)生時(shí),通過(guò)掃描OopMap的數(shù)據(jù)就可以快速標(biāo)識(shí)出存活的對(duì)象。
如何安全的GC?
線程運(yùn)行時(shí),只有在到達(dá)安全點(diǎn)(Safe Point)才能停頓下來(lái)進(jìn)行GC。
基于OopMap數(shù)據(jù)結(jié)構(gòu),HotSpot可以快速完成GC Roots的遍歷,不過(guò)HotSpot并不會(huì)為每條指令都生成對(duì)應(yīng)的OopMap,只會(huì)在Safe Point處記錄這些信息。
所以Safe Point的選擇很重要,如果太少可能導(dǎo)致GC等待的時(shí)間太長(zhǎng),如果太頻繁可能導(dǎo)致運(yùn)行時(shí)的性能問(wèn)題。大部分指令的執(zhí)行時(shí)間都非常短暫,通常會(huì)選擇一些執(zhí)行時(shí)間較長(zhǎng)的指令作為Safe Point,如方法調(diào)用、循環(huán)跳轉(zhuǎn)和異常跳轉(zhuǎn)等。
關(guān)于Safe Point更多的信息,可以看看這篇文章 JVM的Stop The World,安全點(diǎn),黑暗的地底世界
發(fā)生GC時(shí),如何讓所有線程跑到最近的Safe Point再暫停?
當(dāng)發(fā)生GC時(shí),不直接對(duì)線程進(jìn)行中斷操作,而是簡(jiǎn)單的設(shè)置一個(gè)中斷標(biāo)志,每個(gè)線程運(yùn)行到Safe Point的時(shí)候,主動(dòng)去輪詢(xún)這個(gè)中斷標(biāo)志,如果中斷標(biāo)志為真,則將自己進(jìn)行中斷掛起。
這里忽略了一個(gè)問(wèn)題,當(dāng)發(fā)生GC時(shí),運(yùn)行中的線程可以跑到Safe Point后進(jìn)行掛起,而那些處于Sleep或Blocked狀態(tài)的線程在此時(shí)無(wú)法響應(yīng)JVM的中斷請(qǐng)求,無(wú)法到Safe Point處進(jìn)行掛起,針對(duì)這種情況,可以使用安全區(qū)域(Safe Region)進(jìn)行解決。
Safe Region是指在一段代碼片段中,對(duì)象的引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域中的任何位置開(kāi)始GC都是安全的。
1、當(dāng)線程運(yùn)行到Safe Region的代碼時(shí),首先標(biāo)識(shí)已經(jīng)進(jìn)入了Safe Region,如果這段時(shí)間內(nèi)發(fā)生GC,JVM會(huì)忽略標(biāo)識(shí)為Safe Region狀態(tài)的線程;
2、當(dāng)線程即將離開(kāi)Safe Region時(shí),會(huì)檢查JVM是否已經(jīng)完成GC,如果完成了,則繼續(xù)運(yùn)行,否則線程必須等待直到收到可以安全離開(kāi)Safe Region的信號(hào)為止;
垃圾收集器
Java虛擬機(jī)規(guī)范并沒(méi)有規(guī)定垃圾收集器應(yīng)該如何實(shí)現(xiàn),用戶(hù)可以根據(jù)系統(tǒng)特點(diǎn)對(duì)各個(gè)區(qū)域所使用的收集器進(jìn)行組合使用。

上圖展示了7種不同分代的收集器,如果兩兩之間存在連線,說(shuō)明可以組合使用。
1、Serial收集器(串行GC)
Serial 是一個(gè)采用單個(gè)線程并基于復(fù)制算法工作在新生代的收集器,進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程。對(duì)于單CPU環(huán)境來(lái)說(shuō),Serial由于沒(méi)有線程交互的開(kāi)銷(xiāo),可以很高效的進(jìn)行垃圾收集動(dòng)作,是Client模式下新生代默認(rèn)的收集器。
2、ParNew收集器(并行GC)
ParNew其實(shí)是serial的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為與Serial一樣。
3、Parallel Scavenge收集器(并行回收GC)
Parallel Scavenge是一個(gè)采用多線程基于復(fù)制算法并工作在新生代的收集器,其關(guān)注點(diǎn)在于達(dá)到一個(gè)可控的吞吐量,經(jīng)常被稱(chēng)為“吞吐量?jī)?yōu)先”的收集器。
吞吐量 = 用戶(hù)代碼運(yùn)行時(shí)間 /(用戶(hù)代碼運(yùn)行時(shí)間 + 垃圾收集時(shí)間)
Parallel Scavenge提供了兩個(gè)參數(shù)用于精確控制吞吐量:
1、-XX:MaxGCPauseMillis 設(shè)置垃圾收集的最大停頓時(shí)間
2、-XX:GCTimeRatio 設(shè)置吞吐量大小
4、Serial Old收集器(串行GC)
Serial Old 是一個(gè)采用單線程基于標(biāo)記-整理算法并工作在老年代的收集器,是Client模式下老年代默認(rèn)的收集器。
5、Parallel Old收集器(并行GC)
Parallel Old是一個(gè)采用多線程基于標(biāo)記-整理算法并工作在老年代的收集器。在注重吞吐量以及CPU資源敏感的場(chǎng)合,可以?xún)?yōu)先考慮Parallel Scavenge和Parallel Old的收集器組合。
6、CMS收集器(并發(fā)GC)
CMS(Concurrent Mark Sweep)是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,工作在老年代,基于“標(biāo)記-清除”算法實(shí)現(xiàn),整個(gè)過(guò)程分為以下4步:
1、初始標(biāo)記:這個(gè)過(guò)程只是標(biāo)記以下GC Roots能夠直接關(guān)聯(lián)的對(duì)象,但是仍然會(huì)Stop The World;
2、并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing的過(guò)程,可以和用戶(hù)線程一起工作。
3、重新標(biāo)記:用于修正并發(fā)標(biāo)記期間由于用戶(hù)程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那部分記錄,這個(gè)過(guò)程會(huì)暫停所有線程,但其停頓時(shí)間遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短;
4、并發(fā)清理:可以和用戶(hù)線程一起工作。
CMS收集器的缺點(diǎn):
1、對(duì)CPU資源比較敏感,在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶(hù)線程停頓,但是會(huì)占用一部分線程資源,降低系統(tǒng)的總吞吐量。
2、無(wú)法處理浮動(dòng)垃圾,在并發(fā)清理階段,用戶(hù)線程的運(yùn)行依然會(huì)產(chǎn)生新的垃圾對(duì)象,這部分垃圾只能在下一次GC時(shí)收集。
3、CMS是基于標(biāo)記-清除算法實(shí)現(xiàn)的,意味著收集結(jié)束后會(huì)造成大量的內(nèi)存碎片,可能導(dǎo)致出現(xiàn)老年代剩余空間很大,卻無(wú)法找到足夠大的連續(xù)空間分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC。
JDK1.5實(shí)現(xiàn)中,當(dāng)老年代空間使用率達(dá)到68%時(shí),就會(huì)觸發(fā)CMS收集器,如果應(yīng)用中老年代增長(zhǎng)不是太快,可以通過(guò)-XX:CMSInitiatingOccupancyFraction參數(shù)提高觸發(fā)百分比,從而降低內(nèi)存回收次數(shù)提高系統(tǒng)性能。
JDK1.6實(shí)現(xiàn)中,觸發(fā)CMS收集器的閾值已經(jīng)提升到92%,要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿(mǎn)足用戶(hù)線程需要,會(huì)出現(xiàn)一次”Concurrent Mode Failure”失敗,這是虛擬機(jī)會(huì)啟動(dòng)Serial Old收集器對(duì)老年代進(jìn)行垃圾收集,當(dāng)然,這樣應(yīng)用的停頓時(shí)間就更長(zhǎng)了,所以這個(gè)閾值也不能設(shè)置的太高,如果導(dǎo)致了”Concurrent Mode Failure”失敗,反而會(huì)降低性能,至于如何設(shè)置這個(gè)閾值,還得長(zhǎng)時(shí)間的對(duì)老年代空間的使用情況進(jìn)行監(jiān)控。
7、G1收集器
G1(Garbage First)是JDK1.7提供的一個(gè)工作在新生代和老年代的收集器,基于“標(biāo)記-整理”算法實(shí)現(xiàn),在收集結(jié)束后可以避免內(nèi)存碎片問(wèn)題。
G1優(yōu)點(diǎn):
1、并行與并發(fā):充分利用多CPU來(lái)縮短Stop The World的停頓時(shí)間;
2、分代收集:不需要其他收集配合就可以管理整個(gè)Java堆,采用不同的方式處理新建的對(duì)象、已經(jīng)存活一段時(shí)間和經(jīng)歷過(guò)多次GC的對(duì)象獲取更好的收集效果;
3、空間整合:與CMS的”標(biāo)記-清除”算法不同,G1在運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片,有利于應(yīng)用的長(zhǎng)時(shí)間運(yùn)行,且分配大對(duì)象時(shí),不會(huì)導(dǎo)致由于無(wú)法申請(qǐng)到足夠大的連續(xù)內(nèi)存而提前觸發(fā)一次Full GC;
4、停頓預(yù)測(cè):G1中可以建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。
使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大區(qū)別,整個(gè)Java堆會(huì)被劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region,新生代和老年代不再是物理隔離了,都是一部分Region(不需要連續(xù))的集合。G1會(huì)跟蹤各個(gè)Region的垃圾收集情況(回收空間大小和回收消耗的時(shí)間),維護(hù)一個(gè)優(yōu)先列表,根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region,避免在整個(gè)Java堆上進(jìn)行全區(qū)域的垃圾回收,確保了G1收集器可以在有限的時(shí)間內(nèi)盡可能收集更多的垃圾。
不過(guò)問(wèn)題來(lái)了:使用G1收集器,一個(gè)對(duì)象分配在某個(gè)Region中,可以和Java堆上任意的對(duì)象有引用關(guān)系,那么如何判定一個(gè)對(duì)象是否存活,是否需要掃描整個(gè)Java堆?其實(shí)這個(gè)問(wèn)題在之前收集器中也存在,如果回收新生代的對(duì)象時(shí),不得不同時(shí)掃描老年代的話,會(huì)大大降低Minor GC的效率。
針對(duì)這種情況,虛擬機(jī)提供了一個(gè)解決方案:G1收集器中Region之間的對(duì)象引用關(guān)系和其他收集器中新生代與老年代之間的對(duì)象引用關(guān)系被保存在Remenbered Set數(shù)據(jù)結(jié)構(gòu)中,用來(lái)避免全堆掃描。G1中每個(gè)Region都有一個(gè)對(duì)應(yīng)的Remenbered Set,當(dāng)虛擬機(jī)發(fā)現(xiàn)程序?qū)eference類(lèi)型的數(shù)據(jù)進(jìn)行寫(xiě)操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫(xiě)操作,檢查Reference引用的對(duì)象是否處于相同的Region中,如果不是,則通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬Region的Remenbered Set中。
在互聯(lián)網(wǎng)公司面試中,架構(gòu)的底層一定是面試官會(huì)問(wèn)到的問(wèn)題,針對(duì)面試官一般會(huì)提到的問(wèn)題,我錄制了一些分布式,微服務(wù),性能優(yōu)化等技術(shù)點(diǎn)底層原理的錄像視頻,加群619881427可以免費(fèi)獲取這些錄像,里面還有些分布式,微服務(wù),性能優(yōu)化,Spring,MyBatis的等源碼知識(shí)點(diǎn)的錄像視頻。這些視頻都是我找一些資深架構(gòu)師朋友一起錄制出來(lái)的,這些視頻幫助以下幾類(lèi)程序員:
1.對(duì)現(xiàn)在的薪資不滿(mǎn),想要跳槽,卻對(duì)自己的技術(shù)沒(méi)有信心,不知道如何面對(duì)面試官。
2.想從傳統(tǒng)行業(yè)轉(zhuǎn)行到互聯(lián)網(wǎng)行業(yè),但沒(méi)有接觸過(guò)互聯(lián)網(wǎng)技術(shù)。
3.工作1 - 5年需要提升自己的核心競(jìng)爭(zhēng)力,但學(xué)習(xí)沒(méi)有系統(tǒng)化,不知道自己接下來(lái)要學(xué)什么才是正確的,踩坑后又不知道找誰(shuí),百度后依然不知所以然。
4.工作5 - 10年無(wú)法突破技術(shù)瓶頸(運(yùn)用過(guò)很多技術(shù),在公司一直寫(xiě)著業(yè)務(wù)代碼,卻依然不懂底層實(shí)現(xiàn)原理)
如果你現(xiàn)在正處于我上述所說(shuō)的幾個(gè)階段可以加下我的群來(lái)學(xué)習(xí)。而且我也能夠提供一些面試指導(dǎo),職業(yè)規(guī)劃等建議。