1.哪些內(nèi)存需要回收
程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧隨線程而生隨線程而滅。棧中棧幀隨著方法的調(diào)用與執(zhí)行完畢而入棧與出棧,每個(gè)棧幀分配的內(nèi)存基本是類結(jié)構(gòu)確定下來就已知的(盡管在運(yùn)行期會(huì)由即時(shí)編譯器進(jìn)行一些優(yōu)化,但在基于概念模型的討論中,大體可以認(rèn)為編譯其是可知的),因此這幾個(gè)區(qū)域的內(nèi)存分配和回收具備確定性。堆與方法區(qū)則不能確定接口類的內(nèi)存,只有運(yùn)行期間才會(huì)知道創(chuàng)建了哪些對象并動(dòng)態(tài)分配內(nèi)存。垃圾回收器關(guān)注的也就是這部分內(nèi)存。
2.垃圾對象的判定
(1) 引用計(jì)數(shù)法
給對象中添加一個(gè)引用計(jì)數(shù)器,有引用則+1,引用失效-1,任何時(shí)刻計(jì)數(shù)器為0該對象不可用。缺陷:兩個(gè)對象互相循環(huán)引用沒法判斷如objA.instance = objB。objB.instance = objA。
引用計(jì)數(shù)法缺陷demo:
/**
* 引用計(jì)數(shù)算法的缺陷
* testGC方法執(zhí)行后,objA和objB會(huì)不會(huì)被GC呢
* jvm args: -XX:+PrintGCDetails
*
* 會(huì)GC,虛擬機(jī)不是通過引用計(jì)數(shù)算法來判斷對象存活的
*/
public class ReferenceCountuinGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
/**
* 該成員屬性的唯一意義就是占點(diǎn)內(nèi)存,以便能在GC日志中看清楚是否被回收過
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
ReferenceCountuinGC objA = new ReferenceCountuinGC();
ReferenceCountuinGC objB = new ReferenceCountuinGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//假設(shè)在這里發(fā)生GC,objA和objB能回收嗎
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
結(jié)果:GC日志顯示虛擬機(jī)并沒有因?yàn)閮蓚€(gè)對象互相引用就不回收他們,這也從側(cè)面證明了虛擬機(jī)并不是通過引用計(jì)數(shù)法來判斷對象是否存活的。
[GC (System.gc()) [PSYoungGen: 7433K->712K(38400K)] 7433K->720K(125952K), 0.0860753 secs] [Times: user=0.14 sys=0.00, real=0.09 secs]
[Full GC (System.gc()) [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 8K->654K(87552K)] 720K->654K(125952K), [Metaspace: 3475K->3475K(1056768K)], 0.0049854 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
附 GC日志分析
l GC和Full GC說明了這次垃圾收集的停頓類型。如果有“Full”,說明這次GC發(fā)生了STW(Stop-The-World),因?yàn)槭钦{(diào)用了System.gc()方法觸發(fā)的收集,所以會(huì)顯示”[Full GC
(System.gc())”。
l PSYoungGen是采用Parallel
Scavenge收集器的年輕代,ParOldGen是采用Parallel
Old收集器的老年代,Tenured是采用Serial Old收集器的老年代。
l [PSYoungGen:
7433K->712K(38400K)] 表示GC前該內(nèi)存區(qū)域已使用的容量7433K->GC后該內(nèi)存區(qū)域已使用的容量712K(該內(nèi)存區(qū)域總?cè)萘?8400K)
l 7433K->720K(125952K)表示GC前Java堆已使用容量7433K->GC后Java堆已使用容量720K(Java堆總?cè)萘?25952K)”
l 0.0860753secs 代表該內(nèi)存區(qū)域GC所占用的時(shí)間,單位是秒 secs
查看垃圾收集器
java -XX:+PrintCommandLineFlags –version 查看默認(rèn)垃圾回收器命令

附日志屬性解析及垃圾收集器參數(shù)傳送門
日志屬性分析:https://blog.csdn.net/qiaqia609/article/details/50912683
垃圾收集器參數(shù)參照表:https://blog.csdn.net/MakeContral/article/details/79119050
(2) 可達(dá)性分析法
通過一系列的稱為“GC Roots“的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí),就證明此對象是不可用的。

Java中可作為 GC Roots 的對象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象,如各個(gè)線程被調(diào)用的方法堆棧中使用的參數(shù),局部變量,臨時(shí)變量等。
- 方法區(qū)中的類靜態(tài)屬性引用的對象,如java類的引用類型靜態(tài)變量
- 方法區(qū)中的常量引用的對象,如字符串常量池里的引用。
- 本地方法棧中 JNI(Native 方法)的引用對象。
- java虛擬機(jī)內(nèi)部的引用,如基本數(shù)據(jù)類型對應(yīng)的Class對象,一些常駐的異常對象(如NullPointException,OutOfMemoryError)等,還有系統(tǒng)類加載器
- 所有被同步鎖(Synchronized)持有的對象
- 反應(yīng)java虛擬機(jī)內(nèi)部情況的JMXBean、JMTI中注冊的回調(diào)、本地代碼緩存等
(3) 對象引用
無論使用引用計(jì)數(shù)算法判斷對象的引用數(shù)量,還是使用可達(dá)性分析算法判斷對象的引用鏈?zhǔn)欠窨蛇_(dá),判斷對象是否存活都與“引用”有關(guān)。jdk1.2后對象引用與垃圾會(huì)后的關(guān)聯(lián)如下:
- 強(qiáng)引用:永遠(yuǎn)不會(huì)回收。
如“Object obj = new Object()”,這類引用是 Java 程序中最普遍的。只要強(qiáng)引用還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對象。- 軟引用:內(nèi)存溢出之前進(jìn)行回收。
用來描述一些可能還有用,但并非必須的對象。在系統(tǒng)內(nèi)存不夠用時(shí),這類引用關(guān)聯(lián)的對象將被垃圾收集器二次回收,回收后內(nèi)存還是不足,才會(huì)拋出內(nèi)存溢出。場景:如上一個(gè)頁面的鏈接緩存這種。SoftReference 類實(shí)現(xiàn)軟引用SoftReference ref=new SoftReference(new User());- 弱引用:被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾回收之前。
用來描述非必須對象的,但它的強(qiáng)度比軟引用更弱些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對象。WeakReference 類實(shí)現(xiàn)弱引用。WeakReference sr = new WeakReference(new User());- 虛引用:任何時(shí)候都可能被垃圾回收器回收。
最弱的一種引用關(guān)系,完全不會(huì)對其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個(gè)對象實(shí)例。為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的是希望能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。PhantomReference 類來實(shí)現(xiàn)虛引用。
PhantomReference pr = new PhantomReference(new User(), new ReferenceQueue());
附對象引用描述傳送門
四大引用的具體描述:https://www.cnblogs.com/JamesWang1993/p/9347218.html
(4) 對象的自我救贖(了解)
即使在可達(dá)性分析算法中不可達(dá)的對象,可不是“非死不可“的,此時(shí)他們處于“緩刑”
階段。
要真正宣告一個(gè)對象死亡,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行根搜索后發(fā)現(xiàn)沒有與 GC Roots 相連接的引用鏈,那它會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選(篩選的條件是此對象是否有必要執(zhí)行 finalize()方法)。當(dāng)對象沒有覆蓋 finalize()方法,或 finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為沒有必要執(zhí)行。如果該對象被判定為有必要執(zhí)行 finalize()方法,那么這個(gè)對象將會(huì)被放置在一個(gè)名為 F-Queue 隊(duì)列中,并在稍后由一條由虛擬機(jī)自動(dòng)建立的、低優(yōu)先級的 Finalizer 線程去執(zhí)行 finalize()方法。finalize()方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)(因?yàn)橐粋€(gè)對象的 finalize()方法最多只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次),稍后 GC 將對 F-Queue 中的對象進(jìn)行第二次小規(guī)模的標(biāo)記,如果要在 finalize()方法中成功拯救自己,只要在 finalize()方法中讓該對象重新引用鏈上的任何一個(gè)對象建立關(guān)聯(lián)即可。而如果對象這時(shí)還沒有關(guān)聯(lián)到任何鏈上的引用,那它就會(huì)被回收掉。
finalize自我救贖demo:
/**
* finalize 對象自我拯救
* 1對象在被GC時(shí)自我拯救
* 2.這種自救的機(jī)會(huì)只有一次,因?yàn)橐粋€(gè)對象的 finalize()方法最多只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次
* @author wb-szy546826
* @date 2019-6-3 14:34:04
*/
public class FinalizzeEscapeGC {
public static FinalizzeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes, i am still alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method excuted!");
FinalizzeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizzeEscapeGC();
//對象第一次成功救贖自己
SAVE_HOOK = null;
System.gc();
//因?yàn)閒inalize方法優(yōu)先級很低,暫停0.5秒等待
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no, i am dead");
}
//下面這段代碼與上面完全相同,但是自我自救卻失敗了
SAVE_HOOK = null;
System.gc();
//因?yàn)閒inalize方法優(yōu)先級很低,暫停0.5秒等待
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no, i am dead");
}
}
}
結(jié)果:
finalize method excuted!
yes, i am still alive
no, i am dead
finalize 對象自我拯救
(1)對象在被GC時(shí)自我拯救
(2)自救的機(jī)會(huì)只有一次,因?yàn)橐粋€(gè)對象的 finalize()方法最多只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次
(3)不建議使用finalize方法
(5) 方法區(qū)回收
方法區(qū)回收內(nèi)容:廢棄的常量,不再使用的類型(無用的類)。
類需要同時(shí)滿足下面3個(gè)條件才能算是 “無用的類” :
1.該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例。
2.加載該類的 ClassLoader 已經(jīng)被回收。
3.該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
3.垃圾回收算法
分代收集理論
弱分代假說:絕大多數(shù)對象都是朝生夕滅的
強(qiáng)分代假說:熬過多次垃圾收集過程的對象就越難消亡
這兩個(gè)假說奠定了常用垃圾收集器的一致的設(shè)計(jì)原則:收集器應(yīng)該將java堆劃分出不同的區(qū)域,然后將回收對象依據(jù)其年齡分配到不同的區(qū)域存儲(chǔ)(新生代、老年代)
GC名詞定義-深入理解java虛擬機(jī)第三版
- 部分收集(Partial GC):目標(biāo)不是完整收集整個(gè)java堆的垃圾收集
1.新生代(Minor GC/Young GC):目標(biāo)只是新生代的垃圾收集
2.老年代(Major GC/Old GC):目標(biāo)只是老年代的垃圾收集,目前只有CMS收集器會(huì)有單獨(dú)收集老年代的行為。
3.混合(Mixed GC):目標(biāo)是收集整個(gè)新生代以及部分老年代的垃圾收集,目前只有G1收集器有這樣的行為。- 整堆收集(FullGC):收集整個(gè)java堆和方法區(qū)的垃圾收集。
標(biāo)記-清除算法-老年代
首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以反過來,標(biāo)記存活的對象,統(tǒng)一回收所有未被標(biāo)記的對象。
缺點(diǎn)有兩個(gè):第一個(gè)是執(zhí)行效率不穩(wěn)定,如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時(shí)必須進(jìn)行大量標(biāo)記和清除的動(dòng)作,導(dǎo)致標(biāo)記和清除兩個(gè)過 程的執(zhí)行效率都隨對象數(shù)量增長而降低;第二個(gè)是內(nèi)存空間的碎片化問題,標(biāo)記、清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致當(dāng)以后在程序運(yùn)行過程中需要分配較大對象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。

復(fù)制算法-新生代
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。如果內(nèi)存中多數(shù)對象都是存活的,這種算法將會(huì)產(chǎn)生大量的內(nèi)存間復(fù)制的開銷,但對于多數(shù)對象都是可回收的情況,算法需要復(fù)制的就是占少數(shù)的存活對象,而且每次都是針對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時(shí)也就不用考慮有空間碎片的復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配即可。
缺點(diǎn):將可用內(nèi)存縮小為了原來的一半
當(dāng)Survivor空間不足以容納一次Minor GC之后存活的對象時(shí),就需要依賴其他內(nèi)存區(qū)域(實(shí) 際上大多就是老年代)進(jìn)行分配擔(dān)保(Handle Promotion)

標(biāo)記-整理算法-老年代
標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。
缺點(diǎn):移動(dòng)存活對象,尤其是在老年代這種每次回收都有大量對象存活區(qū)域,移動(dòng)存活對象并更新所有引用這些對象的地方將會(huì)是一種極為負(fù)重的操作,而且這種對象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行。

標(biāo)記清除與標(biāo)記整理對比
移動(dòng)存活對象,尤其是在老年代這種每次回收都有大量對象存活區(qū)域,移動(dòng)存活對象并更新所有引用這些對象的地方將會(huì)是一種極為負(fù)重的操作,而且這種移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行(STW)
不移動(dòng)和整理存活對象,彌漫于堆中的存貨對象導(dǎo)致的空間碎片化問題就只能依賴更為復(fù)雜的內(nèi)存分配器和內(nèi)存訪問器來解決。
從對象分配空間方式來看,移動(dòng)對象(規(guī)整堆內(nèi)存),使用指針碰撞(簡單位移指針即可分配內(nèi)存);不移動(dòng)對象(不規(guī)整堆內(nèi)存),使用空閑列表(使用過的與空閑的混在一起,需要維護(hù)一個(gè)列表記錄)。詳見上一章
從垃圾收集停頓時(shí)間來看,不移動(dòng)對象停頓時(shí)間更短-側(cè)面驗(yàn)證CMS基于標(biāo)記-清除注重低停頓,低延遲。
從整個(gè)程序吞吐量來看,移動(dòng)對象更劃算-側(cè)面驗(yàn)證Parallel Scavenge基于標(biāo)記-整理注重吞吐量。
4.垃圾收集器
下面一張圖是HotSpot虛擬機(jī)包含的所有收集器,如果兩個(gè)收集器之間有連線,說明可以搭配使用。

Serial 收集器(復(fù)制算法 -XX:+UseSerialGC)
新生代單線程收集器,標(biāo)記和清理都是單線程,在進(jìn)行垃圾收集時(shí),必須要暫停其他所有的工作線程,直到它收集結(jié)束。優(yōu)點(diǎn)是簡單高效,由于是單線程,此處可以獲取最高的單線程收集效率,但是其中STW停頓時(shí)間長。

Serial Old收集器(標(biāo)記-整理算法 -XX:+UseSerialOldGC)
老年代的單線程收集器,使用標(biāo)記 - 整理算法,運(yùn)行過程同Serial收集器。
ParNew收集器(復(fù)制算法 -XX:+UseParNewGC)
ParNew是Serial的多線程版本,除了使用多線程進(jìn)行垃圾收集外,其他行為與Serial完全一樣,默認(rèn)收集線程數(shù)與cpu核數(shù)相同。除了Serial收集器外,只有ParNew能與CMS收集器配合工作,開啟CMS后默認(rèn)該新生代收集器。
JDK9取消ParNew+Serial Old組合以及Serial+CMS組合,取消-XX:+UseParNewGC參數(shù),這意味著ParNew和CMS只能搭配使用,沒有其他收集器能夠與他們配合了。也可以認(rèn)為ParNew合并到了CMS,成為其處理新生代的組成部分。
Parallel Scavenge收集器(復(fù)制算法 -XX:+UseParallelGC)
Server 模式(內(nèi)存大于2G,2個(gè)cpu)下的 默認(rèn)收集器(Parallel Scavenge+Parallel Old)。
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時(shí)間/(用戶線程時(shí)間+GC線程時(shí)間),高吞吐量可以高效率地利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù),并且虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)調(diào)節(jié)策略。

Parallel Old收集器(標(biāo)記-整理算法 XX:+UseParallelOldGC)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優(yōu)先。
并行:多條垃圾收集線程并行工作,但是此時(shí)用戶線程仍然處理等待狀態(tài)。
并發(fā):用戶線程與垃圾收集線程同時(shí)執(zhí)行(不一定是并行的,可能交替執(zhí)行),用戶程序在繼續(xù)執(zhí)行,而垃圾收程序運(yùn)行在另一個(gè)CPU上
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法)
以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。特點(diǎn)是高并發(fā)、低停頓,追求最短GC回收停頓時(shí)間,cpu占用比較高,響應(yīng)時(shí)間快,停頓時(shí)間短。
過程:
初始標(biāo)記(initial mark,STW): 暫停所有的其他線程,僅僅標(biāo)記下gc roots直接能關(guān)聯(lián)到的對象,速度很快 ;
并發(fā)標(biāo)記(concurrent mark): 從GC Roots的直接聯(lián)系對象開始遍歷整個(gè)對象圖的過程,這個(gè)過程耗時(shí)較長,但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)進(jìn)行。
重新標(biāo)記(remark, STW): 重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間,因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長,遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時(shí)間短
并發(fā)清理(concurrent sweep): 清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象,由于不需要移動(dòng)存活對象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。

缺點(diǎn)
1.對CPU資源敏感,可能導(dǎo)致應(yīng)用程序變慢;雖然不會(huì)導(dǎo)致用戶線程停頓,但卻會(huì)占用一部分線程導(dǎo)致應(yīng)用程序變慢,降低總吞吐量。
2.無法處理浮動(dòng)垃圾,因?yàn)樵诓l(fā)清理階段用戶線程還在運(yùn)行,自然就會(huì)產(chǎn)生新的垃圾,而在此次收集中無法收集他們,只能留到下次收集,這部分垃圾為浮動(dòng)垃圾,同時(shí),由于用戶線程并發(fā)執(zhí)行,所以需要預(yù)留一部分老年代空間提供并發(fā)收集時(shí)程序運(yùn)行使用。要是CMS預(yù)留的內(nèi)存無法滿足程序分配新對象的需要,會(huì)出現(xiàn)一次“并發(fā)失敗”(Concurrent Mode Failure),這時(shí)候虛擬機(jī)將啟動(dòng)后被預(yù)案,凍結(jié)用戶線程,臨時(shí)啟動(dòng)Serial Old收集器老重新進(jìn)行老年代的垃圾收集,這樣停頓時(shí)間就很長了。(-XX:CMSInitiatingOccupancyFraction設(shè)置預(yù)留空間占比,默認(rèn)92%)
3.由于采用的標(biāo)記 - 清除算法,會(huì)產(chǎn)生大量的內(nèi)存碎片,當(dāng)無法找到足夠大的連續(xù)空間來分配當(dāng)前對象,可能會(huì)提前觸發(fā)一次Full GC。當(dāng)JVM不得不進(jìn)行FULL GC時(shí),JVM會(huì)提供參數(shù)開啟碎片合并整理(-XX:+UseCMSCompactAtFullCollection),內(nèi)存整理必須移動(dòng)存活對象,肯定是無法并發(fā)的,停頓時(shí)間自然會(huì)增加。jvm又提供了另一個(gè)參數(shù)解決上邊出現(xiàn)的停頓時(shí)間增加的問題(-XX:+CMSFullGCsBeforeCompaction),該參數(shù)要求CMS收集器執(zhí)行FullGC提前進(jìn)行碎片整理(默認(rèn)0,即每次進(jìn)入FullGC都進(jìn)行碎片整理),以上兩個(gè)參數(shù)Jdk9都已廢棄。
參數(shù)信息
- -XX:+UseConcMarkSweepGC:啟用cms
- -XX:ConcGCThreads:并發(fā)的GC線程數(shù)
- -XX:+UseCMSCompactAtFullCollection:FullGC之后做內(nèi)存碎片整理
- -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后做內(nèi)存碎片整理,默認(rèn)是0,代表每次 FullGC后都會(huì)整理一次碎片
- -XX:CMSInitiatingOccupancyFraction: 當(dāng)老年代使用達(dá)到該比例時(shí)會(huì)觸發(fā)FullGC(默認(rèn)68%)
- -XX:+UseCMSInitiatingOccupancyOnly:只使用設(shè)定的回收閾值(XX:CMSInitiatingOccupancyFraction設(shè)定的值),如果不指定,JVM僅在第一次使用設(shè)定值,后續(xù)則會(huì)自動(dòng)調(diào)整
- -XX:+CMSScavengeBeforeRemark:在CMS GC前啟動(dòng)一次minor gc,目的在于減少老年代對年輕代的引用,降低CMS GC的標(biāo)記階段時(shí)的開銷,一般CMS的GC耗時(shí) 80%都在 remark階段
Garbage First(G1)收集器(分區(qū)/標(biāo)記-整理算法)
G1收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果,開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。
jdk9發(fā)布之日,G1宣告取代Parallel Scavenge+Parallel Old組合,成為服務(wù)端模式下默認(rèn)垃圾收集器,而CMS則淪落至不推薦使用收集器(Deprecate)。
面向局部的設(shè)計(jì)思想
“停頓時(shí)間模型”收集器:指能夠支持在一個(gè)長度為M毫秒內(nèi)的時(shí)間片段內(nèi),消耗在垃圾收集器上的時(shí)間大概率不超過N毫秒這樣的目標(biāo)。
G1之前的垃圾收集器,目標(biāo)范圍要么是整個(gè)新生代,要么是整個(gè)老年代,要么是整個(gè)java堆。G1則跳出該思想,它可以面向堆內(nèi)存任何部分來組成回收集進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代,而是那塊內(nèi)存存放的垃圾數(shù)量最多,回收受益最大,這就是G1收集器的Mixed GC模式,實(shí)現(xiàn)上邊的停頓時(shí)間模型目標(biāo)。
基于Region的內(nèi)存布局形式
G1也仍是遵循分代收集理論設(shè)計(jì)的,但是其堆內(nèi)存的布局與其他收集器有明顯差異,G1不再堅(jiān)持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把java堆華為多個(gè)大小相等的獨(dú)立區(qū)域(Region),每個(gè)Region都可以根據(jù)需要,扮演新生代的Eden,Survivor或者老年代空間(不再固定,不需要連續(xù))。收集器對扮演不同角色的Region采用不同的策略處理。
Region還有一群特殊的Humongous區(qū)域,專門存儲(chǔ)大對象。G1認(rèn)為只要大小超過一個(gè)Region容量一般的對象,即可判定為大對象。每個(gè)Region大小通過參數(shù)-XX:G1HeapRegionSize設(shè)定,取值范圍1MB~32MB,且應(yīng)為2的N次冪。對于超哥整個(gè)Region的超級大對象,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region中,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分進(jìn)行看待。

具有優(yōu)先級的區(qū)域回收方式,建立可預(yù)測的停頓時(shí)間模型
- G1之所以能建立可預(yù)測的停頓時(shí)間模型,是因?yàn)樗鼘egion作為單次回收的最小單元(化整為零),即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍,這樣可以有計(jì)劃的避免在整個(gè)java堆中進(jìn)行全區(qū)域的垃圾收集。
- 具體思路就是讓G1去跟蹤每個(gè)Region里面垃圾堆積的價(jià)值大小(回收所獲得的的空間大小以及回收所需要時(shí)間的經(jīng)驗(yàn)值),然后再后臺(tái)維護(hù)一個(gè)優(yōu)先級列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間(-XX:MaxGCPauseMillis,默認(rèn)200ms),優(yōu)先處理回收價(jià)值is后以最大的Region,這就是“Garbage First”名字的由來。
難點(diǎn)
- 1.跨Region引用對象
記憶集RSet避免全堆作為GC Roots掃描。
存在于新生代引用老年代對象,老年代引用老年代對象。
如某個(gè)新生代對象存在跨代引用,由于老年代對象難以消亡,該引用會(huì)使得新生代對象在收集時(shí)同樣得以存活。我們就不應(yīng)該為了少量的跨代引用去掃描整個(gè)老年代,只需在新生代建立一個(gè)記憶集RSet,RSet標(biāo)識(shí)出老年代的哪一塊內(nèi)存會(huì)存在跨代引用。此后當(dāng)發(fā)生MinorGC時(shí),只有包含了跨代引用的小塊內(nèi)存里的對象才會(huì)被加入到GC Roots進(jìn)行掃描。- 2.并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾的運(yùn)行
1).首先要解決的是用戶線程改變對象引用關(guān)系時(shí),必須保證不能打破原來的對象圖結(jié)構(gòu),導(dǎo)致標(biāo)記結(jié)果出現(xiàn)錯(cuò)誤。CMS通過增量更新算法實(shí)現(xiàn),G1通過原始快照(SATB)實(shí)現(xiàn) --待了解
2).此外,垃圾收集對用戶線程的影響還體現(xiàn)在回收過程中新創(chuàng)建對象的內(nèi)存分配上。G1為每個(gè)Region設(shè)計(jì)了兩個(gè)TAMS指針,把Region中的一部分內(nèi)容劃分出來用于并發(fā)回收過程中的新對象分配,并發(fā)回收時(shí)新分配的對象地址必須在這兩個(gè)指針之上。該指針之上的默認(rèn)存活,不回收。
特點(diǎn)
并行與并發(fā):G1能充分利用CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個(gè)CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時(shí)間。部分其他收集器原本需要停頓Java線程來執(zhí)行GC動(dòng)作,G1收集器仍然可以通過并發(fā)的方式讓java程序繼續(xù)執(zhí)行。
分代收集:雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆,但是還是保留了分代的概念,采用不同的方式處理新建的對象、已經(jīng)存活了一段時(shí)間和熬過多次GC的舊對象獲取更好的收集效果。
空間整合:與CMS的“標(biāo)記--清理”算法不同,G1從整體來看是基于“標(biāo)記整理”算法 實(shí)現(xiàn)的收集器;從局部上來看是基于“復(fù)制”算法實(shí)現(xiàn)的。即G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后仍可提供規(guī)整的可用內(nèi)存。
可預(yù)測的停頓:這是G1相對于CMS的另一個(gè)大優(yōu)勢,降低停頓時(shí)間是G1 和 CMS 共同 的關(guān)注點(diǎn),但G1 除了追求低停頓外,還能建立可預(yù)測的停頓時(shí)間模型,能讓使用者明確指 定在一個(gè)長度為M毫秒的時(shí)間片段(通過參數(shù)"-XX:MaxGCPauseMillis"指定)內(nèi)完成垃圾收集。但是也不能完全無限度的降低停頓時(shí)間,每次停頓目標(biāo)時(shí)間太短,導(dǎo)致每次選出來的回收集只占堆內(nèi)存的一小部分,收集器收集的速度跟不上分配器分配的速度,導(dǎo)致垃圾慢慢堆積。
回收集集合CSet
在垃圾收集過程中收集的Region集合可以稱為收集集合CSet,也就是在垃圾收集暫停過程中被回收的目標(biāo)。G1可以面向堆內(nèi)任何部分來組成CSet進(jìn)行回收。
年輕代收集CSet只收集年輕代分區(qū),而混合收集則會(huì)通過啟發(fā)算法(GC效率),在老年代候選分區(qū)中,篩選出回收收益最高的分區(qū)到CSet中。
G1的分代收集
minorGC
開始生成對象時(shí),G1會(huì)選一個(gè)分區(qū)并指定他為eden分區(qū)。
不是現(xiàn)有的Eden滿了就會(huì)馬上觸發(fā)minorGC,G1會(huì)計(jì)算現(xiàn)在Eden區(qū)回收大概需要多久時(shí)間,如果回收時(shí)間遠(yuǎn)小于(-XX:MaxGCPauseMillis)參數(shù)值,那么動(dòng)態(tài)給Eden增加新的空閑的Region給新對象存放,-XX:G1MaxNewSizePercent參數(shù)代表eden內(nèi)存可以動(dòng)態(tài)添加的最大空間默認(rèn)60%,直到下一次Eden區(qū)存滿, G1重新計(jì)算回收時(shí)間與XX:MaxGCPauseMillis參數(shù)值對比,接近該參數(shù)才會(huì)觸發(fā)minorGC。
minorGC后存活的對象,轉(zhuǎn)移到survivor中,其余的過程與分代收集器中的minorGC一樣
minor gc內(nèi)部回收過程:stw,創(chuàng)建回收集CSet
1掃描根,主要是靜態(tài)和棧幀本地變量表的本地變量被掃描
2更新處理RSet,主要檢測跨region,從年輕代指向老年代的對象
3對象復(fù)制,eden->survivor/old
4處理引用,軟引用,弱引用,虛引用
mixedGC
年輕代收集不斷活動(dòng)后,老年代的空間也會(huì)被逐漸填充。當(dāng)老年代占用空間超過整堆比閾值IHOP(-XX:InitiatingHeapOccupancyPercent默認(rèn)45%)觸發(fā)MixedGC,會(huì)觸發(fā)新生代minorGC,也會(huì)收集部分老年代(根據(jù)期望的GC停頓時(shí)間確定old區(qū)垃圾回收的優(yōu)先順序)。MixedGC主要使用復(fù)制算法,需要把各個(gè)Region存活的對象拷貝到別的Region中,如果拷貝過程發(fā)現(xiàn)沒有足夠的空Region能夠承載拷貝對象則觸發(fā)FullGC。
RSset記錄了什么
新生代對老年代的跨region引用,防止整堆掃描
不需要記錄老年代對新生代的RSet嗎?
不需要,因?yàn)樾律膍inor GC在mixed GC之前,可以保證新生代會(huì)先被清除掉。
老年代對老年代的RSet呢?
可以記錄,防止整堆掃面
cms 重新標(biāo)記 增量更新
g1 最終標(biāo)記 原始快照(STAB)
G1分代收集整體流程
年輕代滿->G1判斷收集時(shí)間->動(dòng)態(tài)擴(kuò)容新生代Region->再次滿,且收集時(shí)間接近配置時(shí)間->觸發(fā)minorGC->老年代增長->老年代占用空間(老年代+Humongous)達(dá)到閾值IHOP(-XX:InitiatingHeapOccupancyPercent)默認(rèn)45%->G1開始準(zhǔn)備收集老年代->mixed gc->初始標(biāo)記->并發(fā)標(biāo)記,最終標(biāo)記->篩選回收(年輕代全部回收,百分百都是垃圾的region全部回收,部分垃圾的region計(jì)算出來,按優(yōu)先機(jī)制多次回收,回收情況按參數(shù)分配-XX:G1MixedGCCountTarget 回收次數(shù)默認(rèn)8,--XX:G1HeapWastePercent:堆浪費(fèi)百分比,當(dāng)G1發(fā)現(xiàn)可被回收的空間小于5%時(shí),就不會(huì)再進(jìn)行混合收集,也就是會(huì)結(jié)束當(dāng)前的回收過程)->拷貝存活對象到新的Region時(shí),沒有足夠空間會(huì)觸發(fā)fullGC
很多博客gc流程
初始標(biāo)記,并發(fā)標(biāo)記,最終標(biāo)記,清理并稱為并發(fā)標(biāo)記過程,并發(fā)標(biāo)記過程后有混合回收(多次)過程;本文按深入理解java虛擬機(jī)中說法,將其理解為為初始標(biāo)記,并發(fā)標(biāo)記,最終標(biāo)記,篩選回收(清理+混合回收多次,篩選范圍為所有年輕代,全是垃圾的老年代region(包含humongous),部分垃圾的老年代region(按gc效率做優(yōu)先級多次回收))
mixed gc內(nèi)部回收過程同minor gc內(nèi)部回收過程
G1整體流程
首先G1把Java分成多個(gè)Region,每個(gè)Region中存放著RSet,G1收集的時(shí)候掃描根節(jié)點(diǎn)GC Roots(如方法棧中的臨時(shí)變量),然后由GC Roots找到直連的對象,然后找到RSet中引用的對象,以這兩類對象進(jìn)行堆的引用標(biāo)記。標(biāo)記完成后把新生代中所有的Region放到CSet,有時(shí)會(huì)觸發(fā)全局標(biāo)記然后選出部分收集效率高的老年代Region加入到Cset中區(qū),然后清理CSet的Region,完成清理。
在R大的帖子中,給出了一個(gè)假象的G1垃圾收集運(yùn)行過程,如下圖所示,在結(jié)合上一小節(jié)的細(xì)節(jié),就可以將G1 GC的正常過程理解清楚了。

fullGC
1.轉(zhuǎn)移失敗的擔(dān)保機(jī)制,如從年輕代轉(zhuǎn)移存活對象,從老年代分區(qū)轉(zhuǎn)移存活對象或者分配巨型對象,無法找到可用的空閑分區(qū)
2.當(dāng)發(fā)生第一個(gè)條件后,G1會(huì)嘗試增加堆使用量,如果擴(kuò)展失敗,那么會(huì)觸發(fā)安全措施機(jī)制同時(shí)發(fā)生full GC
停止系統(tǒng)程序,采用單線程標(biāo)記-清除算法GC,非常耗時(shí)
初始標(biāo)記(initial mark,STW):暫停所有的其他線程,并記錄下gc roots直接能引用的對象,并記錄TAMS的值,讓下一階段用戶線程并發(fā)進(jìn)行時(shí),能正確的在可用的Region分配對象。這個(gè)階段需要停頓線程,但耗時(shí)很短,且是借用進(jìn)行MinorGC的時(shí)候同步的,所以G1在這個(gè)階段沒有額外停頓;
并發(fā)標(biāo)記(concurrent Marking):從GC Root開始對堆中對象進(jìn)行可達(dá)性分析,隊(duì)規(guī)掃描整個(gè)堆中的對象圖,找出要會(huì)后的對象,耗時(shí)較長,不過可與用戶程序并發(fā)執(zhí)行。對象圖掃描完成后,要重新處理STAB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對象。
最終標(biāo)記(remark,STW): 對用戶線程做一個(gè)短暫的暫停,用于處理并打階段結(jié)束后仍遺留下來的最后那少量的STAB記錄。
篩選回收(cleanup,STW):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對各個(gè)Region的回收價(jià)值和成本進(jìn)行 排序,根據(jù)用戶所期望的GC停頓時(shí)間(可以用JVM參數(shù) -XX:MaxGCPauseMillis指定)來制定回收計(jì)劃,可以自由選擇多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對象復(fù)制到空的region中,再清理掉舊Region的全部空間。這里操作涉及存活對象的移動(dòng),必須暫停用戶線程,由多條收集器線程并行完成。

參數(shù)信息
-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的線程數(shù)量
-XX:G1HeapRegionSize:指定分區(qū)大小(1MB~32MB,且必須是2的冪),默認(rèn)將整堆劃分為 2048個(gè)分區(qū)
-XX:MaxGCPauseMillis:目標(biāo)暫停時(shí)間(默認(rèn)200ms)
-XX:G1NewSizePercent:新生代內(nèi)存初始空間(默認(rèn)整堆5%)
-XX:G1MaxNewSizePercent:新生代內(nèi)存最大空間
-XX:TargetSurvivorRatio:Survivor區(qū)的填充容量(默認(rèn)50%),Survivor區(qū)域里的一批對象(年齡 1+年齡2+年齡n的多個(gè)年齡對象)總和超過了Survivor區(qū)域的50%,此時(shí)就會(huì)把年齡n(含)以上的對象都放入老年代
-XX:MaxTenuringThreshold:最大年齡閾值(默認(rèn)15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空間達(dá)到整堆內(nèi)存閾值(默認(rèn)45%),則執(zhí)行 新生代和老年代的混合收集(MixedGC),比如我們之前說的堆默認(rèn)有2048個(gè)region,如果有接近 1000個(gè)region都是老年代的region,則可能就要觸發(fā)MixedGC了
-XX:G1HeapWastePercent(默認(rèn)5%): gc過程中空出來的region是否充足閾值,在混合回收的時(shí) 候,對Region回收都是基于復(fù)制算法進(jìn)行的,都是把要回收的Region里的存活對象放入其他 Region,然后這個(gè)Region中的垃圾對象全部清理掉,這樣的話在回收過程就會(huì)不斷空出來新的 Region,一旦空閑出來的Region數(shù)量達(dá)到了堆內(nèi)存的5%,此時(shí)就會(huì)立即停止混合回收,意味著 本次混合回收就結(jié)束了。
-XX:G1MixedGCLiveThresholdPercent(默認(rèn)85%) region中的存活對象低于這個(gè)值時(shí)才會(huì)回收 該region,如果超過這個(gè)值,存活對象過多,回收的的意義不大。
-XX:G1MixedGCCountTarget:在一次回收過程中指定做幾次混合回收(默認(rèn)8次),在混合回收階段可以回收一會(huì),然后暫?;厥?,恢復(fù)系統(tǒng)運(yùn)行,一會(huì)再開始回收,這樣可以讓系統(tǒng)不至于單次停頓時(shí)間過長。
g1收集器推薦文集:https://www.cnblogs.com/GrimMjx/p/12234564.html
https://blog.csdn.net/coderlius/article/details/79272773
https://www.cnblogs.com/yufengzhang/p/10571081.html
https://baijiahao.baidu.com/s?id=1663956888745443356&wfr=spider&for=pc
5.垃圾回收器特點(diǎn)總結(jié)
| 收集器 | 特點(diǎn) |
|---|---|
| serial | 單線程 新生代 復(fù)制 |
| parnew | 多線程并行 新生代 復(fù)制 |
| parallel scavenge | 多線程并行 新生代 復(fù)制 注重吞吐量 |
| serial old | 單線程 老生代 標(biāo)記整理 |
| parallel old | 多線程并行 老生代 標(biāo)記整理 |
| cms | 多線程并發(fā) 老年代 標(biāo)記清除 最短停頓時(shí)間 (初始標(biāo)記+并發(fā)標(biāo)記+重新標(biāo)記+并發(fā)清理) 兩次stw(初始階段+重新標(biāo)記) 初始標(biāo)記簡單標(biāo)記1次stw,重新標(biāo)記解決并發(fā)標(biāo)記時(shí)程序運(yùn)作時(shí)產(chǎn)生的垃圾記錄2次stw 缺點(diǎn):1.cpu敏感,占用cpu線程=(cpu數(shù)量+3)/4 2.cms無法處理浮動(dòng)垃圾,出現(xiàn)concurrent mode failure導(dǎo)致fullGC,原因:并發(fā)清理用戶程序運(yùn)行產(chǎn)生新的垃圾 3.標(biāo)記-清除,產(chǎn)生不連續(xù)空間碎片,導(dǎo)致提前fullGC |
| g1 | 多線程并發(fā)+并行 老年代+新生代 全局標(biāo)記整理,局部復(fù)制(無碎片化問題) 并行并發(fā),分代收集,空間整合,可預(yù)測停頓 younggc動(dòng)態(tài)調(diào)節(jié) mixedgc(初始標(biāo)記+并發(fā)標(biāo)記+最終標(biāo)記+篩選回收) 化整為零 |
其他收集器
后面還有ZGC(JDK11中的垃圾收集器)、Shenandoah GC(JDK12中的垃圾收集器)、C4 GC(Zing JVM)...
6.內(nèi)存分配與回收策略
對象優(yōu)先在Eden分配
大多數(shù)情況下,對象在新生代中 Eden區(qū)分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC。垃圾收集期間發(fā)現(xiàn)已有對象無法全部放入Survivor空間,會(huì)通過分配擔(dān)保機(jī)制提前轉(zhuǎn)移到老年代去。
大對象直接進(jìn)入老年代
大對象就是需要大量連續(xù)內(nèi)存空間的對象,-XX:PretenureSizeThreshold 可以設(shè)置大對象的大小,如果對象超過設(shè)置大小會(huì)直接進(jìn)入老年代,不會(huì)進(jìn)入年輕代,這個(gè)參數(shù)只在 Serial 和ParNew兩個(gè)收集器下有效
長期存活的對象進(jìn)入老年代
虛擬機(jī)給每個(gè)對象一個(gè)對象年齡(Age)計(jì)數(shù)器,存儲(chǔ)在對象頭中。通過參數(shù)-XX:MaxTenuringThreshold來設(shè)置最大年齡,默認(rèn)15。
動(dòng)態(tài)對象年齡判斷
如果在Survivor空間中相同年齡所有對象大小的總和大于這塊Survivor區(qū)域內(nèi)存大小的50%,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無須等到-XX:MaxTenuringThreshold中要求的年齡。對象動(dòng)態(tài)年齡判斷機(jī)制一般是在minor gc之后觸發(fā)的。
空間分配擔(dān)保
年輕代每次minor gc之前JVM都會(huì)計(jì)算下老年代剩余可用空間
如果這個(gè)可用空間小于年輕代里現(xiàn)有的所有對象大小之和,就會(huì)看一個(gè)“-XX:-HandlePromotionFailure”(是否允許擔(dān)保失敗,jdk1.8默認(rèn)就設(shè)置)的參數(shù)是否設(shè)置了,如果有這個(gè)參數(shù),就會(huì)看看老年代的可用內(nèi)存大小,是否大于之前每一次minor gc后進(jìn)入老年代的對象的平均大小。
如果上一步結(jié)果是小于或者之前說的參數(shù)沒有設(shè)置,那么就會(huì)觸發(fā)一次Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對象就會(huì)發(fā)生"OOM"
當(dāng)然,如果minor gc之后剩余存活的需要挪動(dòng)到老年代的對象大小還是大于老年代可用空間,那么也會(huì)觸發(fā)full gc,full gc完之后如果還是沒有空間放minor gc之后的存活對象,則也會(huì)發(fā)生“OOM”