HotSpot算法實現(xiàn)
枚舉根節(jié)點:從GC Roots節(jié)點中找出引用鏈的操作策治。
GC Roots對象主要在全局性引用(常量/類靜態(tài)屬性)與執(zhí)行上下文(棧幀中的本地變量表)中厉膀,虛擬機不會逐個檢查這些地方,HotSpot使用OopMap數(shù)據(jù)結(jié)構(gòu)來記錄觅廓。當(dāng)類加載完成時鼻忠,OopMap中就記錄下對象內(nèi)為引用的位置,和棧杈绸、寄存器中引用的位置帖蔓。當(dāng)GC掃描時就可以直接得知引用信息矮瘟。
Stop The World:在可達性分析期間,需要這期間的一致性——不能一邊分析塑娇,對象的引用關(guān)系還一直變化澈侠,所以GC時必須停頓所有的Java執(zhí)行線程。
安全點:為了避免對每條指令都生成OopMap埋酬,而選擇的記錄信息的特定的位置哨啃。只有在安全點,程序才會停下來GC写妥。安全點的選定標(biāo)準(zhǔn):是否具有讓程序長時間執(zhí)行的特征拳球,例如,方法調(diào)用珍特、循環(huán)跳轉(zhuǎn)祝峻、異常跳轉(zhuǎn)。
讓所有線程都到最近的安全點再停頓的方法:
- 搶先式中斷:先中斷所有線程次坡,再讓不在安全點的線程跑到安全點中斷呼猪。(幾乎不用)
- 主動式中斷:設(shè)置標(biāo)志,各線程主動輪詢標(biāo)志砸琅,發(fā)現(xiàn)中斷標(biāo)志為真時就自己中斷掛起宋距。其中輪詢標(biāo)志的地方和安全點重合。
安全區(qū)域:針對不執(zhí)行的線程(sleep或blocked狀態(tài))設(shè)置症脂。當(dāng)線程進入安全區(qū)時谚赎,就標(biāo)識自己,GC時就不用管這些線程诱篷。當(dāng)線程要離開安全區(qū)時壶唤,要先檢查系統(tǒng)是否完成根節(jié)點枚舉(或GC),要完成了才能安全離開安全區(qū)棕所。
垃圾收集器
新生代垃圾收集器
- 對于單CPU環(huán)境闸盔,ParNew收集器不如Serial收集器效果好。其默認(rèn)開啟的GC線程數(shù)=CPU數(shù)量琳省,也可以使用-XX:ParallelGCThreads設(shè)置線程數(shù)迎吵。
- 其他收集器專注縮短停頓用戶線程的時間,Parallel Scavenge專注于控制吞吐量针贬。吞吐量=CPU執(zhí)行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)击费。高吞吐量即意味著可以高效利用CPU,盡快執(zhí)行完客戶代碼桦他。
-XX:MaxGCPauseMillis:設(shè)定GC停頓的時間
-XX:GCTimeRatio:設(shè)定垃圾收集時間占總時間的比率蔫巩,即吞吐量的倒數(shù)。
-XX:+UseAdaptiveSizePolicy:打開此參數(shù)后,虛擬機動態(tài)調(diào)整新生代大小圆仔、Eden與Survivor區(qū)的比例垃瞧、晉升老年代對象大小等細(xì)節(jié)的參數(shù)。
老年代垃圾收集器
CMS收集器(Concurrent Mark Sweep)
- 以獲取最短停頓時間為目標(biāo)的收集器
- 適用于追求停頓時間最短的B/S系統(tǒng)服務(wù)端
-
基于標(biāo)記-清楚算法
CMS運行圖.jpg
- 初始標(biāo)記(CMS initial mark)
STOP THE WORLD荧缘,標(biāo)記GC Roots能直接關(guān)聯(lián)的對象皆警,速度最快 - 并發(fā)標(biāo)記(CMS concurrent mark)
進行GC Roots Tracing - 重新標(biāo)記(CMS remark)
STOP THE WORLD,修正在并發(fā)標(biāo)記期間有變動的對象標(biāo)記記錄 - 并發(fā)清除(CMS concurrent sweep)
回收對象
缺點
- 對CPU資源敏感
在并發(fā)標(biāo)記和并發(fā)清理截粗,會占用CPU資源信姓,導(dǎo)致程序變慢,總吞吐量降低绸罗。CMS默認(rèn)啟動的回收線程數(shù)=(CPU數(shù)量+3)/4意推,當(dāng)CPU較少時,會對程序產(chǎn)生很大影響珊蟀。 - 無法處理浮動垃圾(Floating Garbage)
浮動垃圾指菊值,在并發(fā)清理階段產(chǎn)生的新的需要回收的對象。使用CMS收集器育灸,需要預(yù)留一部分內(nèi)存給用戶線程使用腻窒,如果在并發(fā)清理期間,預(yù)留內(nèi)存無法滿足程序需要磅崭,就會出現(xiàn)"Concurrent Mode Failure"儿子,然后臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,導(dǎo)致停頓時間變長砸喻∪岜疲可以使用參數(shù)-XX:CMSInitiatingOccupancyFraction
來設(shè)置CMS觸發(fā)的百分比。 - 空間碎片
CMS使用標(biāo)記-清理算法割岛,會產(chǎn)生大量空間碎片愉适。空間碎片過多時會影響大對象的分配癣漆。使用-XX:+UseCMSCompactAtFullCollection
開關(guān)參數(shù)维咸,在要進行Full GC前,開啟內(nèi)存碎片的合并整理過程惠爽。但此操作不能并發(fā)癌蓖,會導(dǎo)致停頓時間增加。-XX:CMSFullGCsBeforeCompaction
則設(shè)置疆股,在執(zhí)行n次不壓縮的Full GC后,執(zhí)行一次碎片整理的Full GC倒槐。
G1收集器(Garbage-first)
適用于服務(wù)器端旬痹。
優(yōu)點:
- 并行與并發(fā)
與CMS相似。 - 分代收集
可以不與其他收集器配合,獨立管理整個GC堆两残,并在不同的代里使用不同的收集算法永毅。 - 空間整合
G1運作期間不會產(chǎn)生內(nèi)存空間碎片,有利于程序長時間運行人弓,不會因為沒有足夠的空間分配給大對象而提前觸發(fā)GC沼死。 -
可預(yù)測停頓
可以建立可預(yù)測的停頓時間模型,讓使用者明確指定在一個長度為M毫秒的時間片段中崔赌,消耗在垃圾收集上的時間不超過N毫秒意蛀。
G1將Java堆劃分為多個大小相等的獨立區(qū)域(Region),雖然也分新生代健芭、老年代县钥,但他們都是一部分Region的集合,不存在物理隔離慈迈。G1根據(jù)各個Region中垃圾堆積的價值大腥糁(回收后可以獲得的空間大小和回收所需時間的經(jīng)驗值),在后臺維護一個優(yōu)先列表痒留,每次根據(jù)用戶允許的收集時間谴麦,優(yōu)先回收價值最大的Region。其難點在于伸头,Region不是孤立的匾效,一個對象會被其他Region的對象引用。在做可達性分析尋找可回收對象時熊锭,需要掃描整個Java堆才能保證準(zhǔn)確性弧轧。G1使用Remembered Set來避免全堆掃描。每個Region對應(yīng)一個Remembered Set碗殷,當(dāng)程序?qū)σ妙愋偷臄?shù)據(jù)進行寫操作時精绎,虛擬機產(chǎn)生一個Write Barrier暫時中斷操作,檢查引用的對象是否處于不同的Region锌妻,如果是的話代乃,就通過CardTable把相關(guān)應(yīng)用信息記錄到被引用對象所屬的Region的Remembered Set中。進行內(nèi)存回收的時候仿粹,在GC根節(jié)點枚舉范圍加上Remembered Set搁吓。
G1收集器運行圖.jpg
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 最終標(biāo)記
STOP THE WORLD,可并發(fā)執(zhí)行最終標(biāo)記吭历。修正并發(fā)標(biāo)記期間有變動的對象記錄堕仔,虛擬機將這些變化記錄在線程Remembered Set Logs里面,再合并到Remembered Set中晌区。 - 篩選回收
STOP THE WORLD摩骨,可并發(fā)執(zhí)行通贞。對各個Region的回收價值和成本進行排序,根據(jù)用戶的需求來指定回收計劃恼五。
GC日志
// 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
[GC (Allocation Failure) [PSYoungGen: 7128K->616K(9216K)] 7128K->6768K(19456K), 0.0057651 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 616K->0K(9216K)] [ParOldGen: 6152K->6658K(10240K)] 6768K->6658K(19456K), [Metaspace: 2542K->2542K(1056768K)], 0.0066751 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
- GC/FULL GC:說明垃圾回收的停頓類型昌罩,full gc是發(fā)生了stop the world的。
- PSYoungGen/ParOldGen:發(fā)生GC的區(qū)域灾馒。
PSYoungGen:Parallel Scavenge收集器新生代
DefNew:Default New Generation茎用,使用Serial收集器中的新生代
ParNew:ParNew收集器的新生代
ParOldGen:Parallel Old 的老年代 - 7128K->616K(9216K):GC前該內(nèi)存區(qū)域已使用容量->GC后已使用容量(總?cè)萘浚?/li>
- 7128K->6768K(19456K)(方括號外):GC前java堆已使用容量->GC后已使用容量(java堆總?cè)萘浚?/li>
- 0.0057651 secs:GC所占用時間(秒)。
- [Times: user=0.00 sys=0.00, real=0.01 secs] :【用戶態(tài)消耗CPU時間睬罗,內(nèi)核態(tài)消耗CPU時間轨功,墻鐘時間(Wall Clock Time)】。墻鐘時間 = 非運算等待耗時(磁盤IO/線程阻塞等待時間) + CPU時間傅物。
內(nèi)存分配與回收策略
-
優(yōu)先在Eden上分配
優(yōu)先在Eden上分配夯辖,Eden空間不夠時,發(fā)起Minor GC董饰,將存活對象移到Survivor中去蒿褂。 -
大對象直接進入老年代
大對象,即需要大量連續(xù)內(nèi)存空間的Java對象(如很長的字符串卒暂、數(shù)組)啄栓。通過-XX:PretenureSizeThreshold
參數(shù),大于這個值的對象都會直接在老年代中進行分配也祠。 -
長期存活對象進入老年代
計算對象的對象年齡計數(shù)器昙楚,新生代的對象每熬過一次Minor GC,年齡就加1诈嘿。年齡超過-XX:MaxTenuringThreshold
參數(shù)的對象堪旧,就會被晉升到老年代。 -
動態(tài)對象年齡判斷
在Survivor空間中奖亚,相同年齡所有對象的大小總和大于Survivor空間的一般時淳梦,大于這個年齡的對象就會被晉升到老年代。 -
空間分配擔(dān)保
Minor GC前昔字,虛擬機先檢查老年代的最大可用的連續(xù)空間爆袍,是否大于新生代所有對象總空間。如果不成立作郭,就查看HandlePromotionFailure
設(shè)置的值是否允許擔(dān)保失敗陨囊。如果允許,檢查老年代最大可用的連續(xù)空間夹攒,是否大于歷次晉升到老年代的對象的平均大小蜘醋。如果大于,就嘗試進行Minor GC咏尝,否則就進行Full GC压语。