參考:《深入理解JVM——高級(jí)特性與最佳實(shí)踐》
一、對(duì)象生存周期
程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧隨線程生滅,不需要GC。GC關(guān)注的主要是java堆和方法區(qū)。
引用計(jì)數(shù)法
缺點(diǎn):很難解決對(duì)象之間相互循環(huán)引用的問題。
可達(dá)性分析算法
通過一系列程為“GC Roots”的對(duì)象作為起始點(diǎn),從這些及誒單開始向下搜索,所走過的路徑稱為“引用鏈-Reference Chain”。
可作為GC Roots的對(duì)象包括下面幾種:
虛擬機(jī)棧(棧幀中的本地變量表)中的引用對(duì)象
方法區(qū)中類靜態(tài)屬性引用的對(duì)象
方法區(qū)中常量引用的對(duì)象
本地方法中JNI(即native方法)引用的對(duì)象
引用:reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址。
分為
強(qiáng)引用(只要強(qiáng)引用還存在,關(guān)聯(lián)的永遠(yuǎn)不會(huì)被回收)、
軟引用(系統(tǒng)將要OOM時(shí),將關(guān)聯(lián)的對(duì)象列進(jìn)回收范圍進(jìn)行二次回收)、
弱引用(無論當(dāng)前內(nèi)存是否夠用,都回收)、
虛引用(為一個(gè)對(duì)象設(shè)置虛應(yīng)用的唯一目的就是對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知)
reference類型:Java的數(shù)據(jù)類型分為兩類:primitive和reference類型。primitive類型可以hold數(shù)字和布爾數(shù)據(jù);reference類型可以hold對(duì)象,接口和數(shù)組類型的數(shù)據(jù)的指針??梢韵胂蠛笠环N數(shù)據(jù)比較復(fù)雜,往往是一段數(shù)據(jù),不像primitive的數(shù)據(jù)是比較單純的數(shù)據(jù)單元。
java堆中對(duì)象回收過程:可達(dá)性分析之后,第一次標(biāo)記-->第一次篩選,對(duì)象覆蓋finalize()方法且沒有執(zhí)行過-->對(duì)象放置在F-Queue隊(duì)列-->由一個(gè)徐及其自動(dòng)建立、低優(yōu)先級(jí)的Finalize線程執(zhí)行-->GC進(jìn)行第二次標(biāo)記,此時(shí)如果對(duì)象重新與引用鏈上任何一個(gè)對(duì)象建立關(guān)聯(lián),則被移除“即將回收”集合。
也就是說一個(gè)對(duì)象的finalize()被執(zhí)行,但依然可以存活。
注意:任何一個(gè)對(duì)象的finalize()方法都只被系統(tǒng)自動(dòng)調(diào)用一次,如果兌現(xiàn)面臨下一次回收,finalize方法不會(huì)再次執(zhí)行。因此只能“自救”一次。
System.gc():代碼顯示調(diào)用GC.-XX:+DisableExplicitGC,這個(gè)參數(shù)作用是禁止代碼中顯示調(diào)用GC。如果加上了這個(gè)JVM啟動(dòng)參數(shù),那么代碼中調(diào)用System.gc()沒有任何效果,相當(dāng)于是沒有這行代碼一樣。
方法區(qū)廢棄常量回收:沒有任何對(duì)象引用常量池中這個(gè)常量,則被清出常量池
方法區(qū)類的回收:判斷廢棄類需要同時(shí)滿足三個(gè)條件——①所有實(shí)例已被回收②加載該類的Class、Loader已回收③該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
而具體是否回收,由參數(shù)控制:-Xnoclassgc -verbese:class -XX:+TraceClassLoading -XX:+TraceClassUnLoading
二、GC算法
標(biāo)記-清除算法:先標(biāo)記,后清除
缺點(diǎn):①標(biāo)記和清除的效率都不高②會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片
復(fù)制算法:內(nèi)存劃分為兩塊,每次只使用其中的一塊。一塊的內(nèi)存用完就把存活的對(duì)象復(fù)制到另一塊,然后清空自己。
缺點(diǎn):將內(nèi)存縮小為原來的一半。
運(yùn)用:回收新生代,不需要按1:1劃分內(nèi)存。
標(biāo)記-整理:讓所有存活的對(duì)象向一端移動(dòng)。適用于老年代。
分代收集法:把java堆分為新生代和老年代。
三、HotSpot算法實(shí)現(xiàn)
枚舉根節(jié)點(diǎn):可達(dá)性分析GC Roots節(jié)點(diǎn)主要在全局性引用(常量或靜態(tài)屬性)與執(zhí)行上下文(棧幀中的本地變量表)中,如果要逐個(gè)檢查會(huì)消耗很多時(shí)間;GC停頓(在分析期間整個(gè)執(zhí)行系統(tǒng)被凍結(jié)在某個(gè)時(shí)間點(diǎn))也會(huì)影響時(shí)間的敏感性。
精確GC:準(zhǔn)確區(qū)分指針和非指針,虛擬機(jī)直接得知哪些地方存放著對(duì)象引用。OopMap記錄對(duì)象內(nèi)什么偏移量上是什么數(shù)據(jù)類型。
safePoint:程序執(zhí)行時(shí)并非在所有地方都能停下來開始GC,只有在安全點(diǎn)才能停下來。一般是方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等“長時(shí)間執(zhí)行”的指令。
搶斷式中斷:GC發(fā)生時(shí)所有線程都中斷,然后恢復(fù)那些不在安全點(diǎn)的線程,跑到安全點(diǎn)。------不建議使用
主動(dòng)式中斷:設(shè)置中斷標(biāo)志,線程跑到安全點(diǎn)時(shí)輪詢這個(gè)標(biāo)志,自己中斷掛起。
saveRegion:一段代碼片段中,引用關(guān)系不發(fā)生變化,包括線程處于sleep和blocked的狀態(tài)。
線程執(zhí)行到安全區(qū),標(biāo)識(shí)自己,那么當(dāng)GC時(shí)就不用管已標(biāo)識(shí)的線程了;在線程需要離開安全區(qū)時(shí),會(huì)檢查GC是否已經(jīng)完成。
四、垃圾收集器
Serial:單線程,stop the world。新生代Serial,復(fù)制算法;老年代SerialOld,標(biāo)記整理算法??煽貐?shù):Eden與Survivor區(qū)的比例-XX:SurvivorRatio、晉升老年代對(duì)象年齡-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure
parNew:Serial的多線程版本。使用-XX:+UseConcMarkSweepGC選項(xiàng)后的默認(rèn)新生代回收器,也可以使用-XX:UseParNewGC強(qiáng)制指定。默認(rèn)開啟的收集線程數(shù)與CPU數(shù)量相同,在CPU非常多(32個(gè)),可以使用-XX:ParallelGCThreads來限制垃圾回收線程數(shù)。
并行(Parallel):多條垃圾回收線程并行工作,用戶線程等待。
并發(fā)(Concurrent):用戶程序繼續(xù)運(yùn)行,垃圾收集程序運(yùn)行于另一個(gè)CPU上。
Parallel Secvenge:復(fù)制算法,并行多線程。CMS等收集器主要是盡可能縮短GC時(shí)間,而Parallel Scavenge是達(dá)到一個(gè)可控制的吞吐量(=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+GC時(shí)間))。最大垃圾收集停頓時(shí)間(大于0的ms)-XX:MaxGCPauseMillis,吞吐量大?。ù笥?小于100的整數(shù)):-XX:GCTimeRatio,打開-XX:UseAdaptiveSizePolicy參數(shù)就不需要指定Xmn,-XX:SurvivorRatio和-XX:PretenureSizeThreshold等細(xì)節(jié)參數(shù)了。虛擬機(jī)會(huì)根據(jù)當(dāng)前運(yùn)行情況收集性能動(dòng)態(tài)調(diào)整最合適的停頓時(shí)間或最大吞吐量,稱為自適應(yīng)調(diào)節(jié)策略(GC Ergonomics)。
Serial Old:作為CMS的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure 時(shí)使用。
Parallel Old:使用多線程和“標(biāo)記-整理”算法。
CMS(Concurrent Mark Sweep):標(biāo)記-清除,目標(biāo)獲取最短回收停頓時(shí)間。
- GC過程如下:
- 初始標(biāo)記:stop the world, 只標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象。
- 并發(fā)標(biāo)記:GC Roots Tracing的過程,與用戶線程一起工作。
- 重新標(biāo)記:stop the world,修正并發(fā)標(biāo)記期間因用戶程序運(yùn)行而導(dǎo)致的標(biāo)記變動(dòng)。停頓時(shí)間大于初始標(biāo)記小于并發(fā)標(biāo)記。
- 并發(fā)清除:與用戶線程一起工作。
- 缺點(diǎn):
- 在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶線程停頓,但是會(huì)因?yàn)檎加昧艘徊糠志€程而導(dǎo)致應(yīng)用程序變慢,吞吐量會(huì)降低。
- 無法處理浮動(dòng)垃圾(并發(fā)階段產(chǎn)生的、本次GC無法回收的垃圾),可能出現(xiàn)“concurrent mode failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。CMS不能等老年代幾乎填滿再回收,需要預(yù)留一部分給并發(fā)收集時(shí)的程序使用,因此提高參數(shù)-XX:CMSInitiatingOccupancyFraction的值來提高出發(fā)百分比(默認(rèn)68%),以便減低回收次數(shù)提高性能。要是運(yùn)行期間預(yù)留內(nèi)存無法滿足需求,發(fā)生“concurrent mode failure”,啟動(dòng)Serial Old后備預(yù)案。
- 碎片空間過多,給大對(duì)象分配帶來麻煩,需要執(zhí)行FullGC。-XX:+UseCMSCompactAtFullCollection用于在CMS頂不住要進(jìn)行FullGC時(shí)開啟內(nèi)存碎片的合并整理過程。碎片整理時(shí)間無法并發(fā),停頓時(shí)間變長;提供參數(shù)-XX:CMSFullGCBeforeCompaction,設(shè)置執(zhí)行多少次不壓縮的Full GC后執(zhí)行一次壓縮的(默認(rèn)為0,每次都?jí)嚎s)。
G1:最新成果。優(yōu)點(diǎn):并行與并發(fā)、分代收集、空間整合、可預(yù)測(cè)的停頓。
將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region,新、老聲帶不再是物理隔離,是一部分不需要連續(xù)的Region的集合。G1跟蹤每個(gè)Region中垃圾堆積的價(jià)值大?。ɑ厥斋@得的空間大小以及所需要的時(shí)間)在后臺(tái)維護(hù)一個(gè)優(yōu)先劣列表。
RemenberedSet:每一個(gè)Region都有一個(gè)對(duì)應(yīng)的RemenberedSet,虛擬機(jī)發(fā)現(xiàn)愛你程序在對(duì)Refenrence類型的數(shù)據(jù)進(jìn)行寫操作時(shí),會(huì)產(chǎn)生一個(gè)WriteBarrier暫時(shí)中斷寫操作,檢查Reference引用的對(duì)象是否處于不同的Region中。如果是,把引用信息記錄到被引用對(duì)象所屬Region的RemenberedSet中。GC時(shí)可保證不對(duì)全堆進(jìn)行掃描也不會(huì)遺漏。
- GC過程如下:
初始標(biāo)記:stop the world, 標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,修改TAMS值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí)能在正確可用的Region中創(chuàng)建新對(duì)象。
并發(fā)標(biāo)記:GC Roots Tracing的過程,找出存活對(duì)象,與用戶線程一起工作。
最終標(biāo)記:stop the world,修正并發(fā)標(biāo)記期間因用戶程序運(yùn)行而導(dǎo)致的標(biāo)記變動(dòng)。這一部分記錄在RemenberedSetLog中,最終合并到RemenberedSet中,并行。
篩選回收:對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,制定回收計(jì)劃。因?yàn)橹换厥找徊糠諶egion,停頓用戶線程(也可以不停頓,并發(fā))將大幅度提高收集效率。
GC日志
開啟GC日志:JVM 加啟動(dòng)參數(shù) -Xloggc:<file>,-XX:+PrintGC或-verbose:gc
五、內(nèi)存分配策略
對(duì)象優(yōu)先新生代在Eden區(qū)分配,當(dāng)沒有足夠空間,虛擬機(jī)發(fā)起一次MinorGC。
MinorGC:新生代的GC
MajorGC/FullGC:老年代的GC,經(jīng)常伴隨著一次MinorGC。一般速度比MinorGC慢10倍以上。
大對(duì)象直接進(jìn)入老年代。參數(shù)-XX:PretenureSizeThreshold大于這個(gè)值的對(duì)象直接在老年代分配,避免在Eden和兩個(gè)Survivor之前發(fā)生大量復(fù)制。
長期存活對(duì)象進(jìn)入老年代。對(duì)象在Eden出生,每經(jīng)過一次MinorGC還存活且能被移入Survivor的話年齡加1。年齡達(dá)到閾值(默認(rèn)15,通過參數(shù)-XX:MaxTenuringThreshold設(shè)置)晉升如老年代。
動(dòng)態(tài)對(duì)象年齡判定:并不是永遠(yuǎn)要求對(duì)象的年齡必須打到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于等于該年齡的對(duì)象就可以直接進(jìn)入老年代。
空間分配擔(dān)保:只要老年代的連續(xù)空間大于新生代對(duì)象總大小或者歷次晉升的平均大小就會(huì)進(jìn)行MinorGC,否則進(jìn)行FullGC。