垃圾回收.GC
1.垃圾判斷算法
1.1 引用計數(shù)算法
給對象添加一個引用計數(shù)器,當(dāng)有一個地方引用它,計數(shù)器加1呻待,當(dāng)引用失效笛粘,計數(shù)器減1趁怔,任何時刻計數(shù)器為0的對象就是不可能再被使用的。
引用計數(shù)器無法解決對象循環(huán)引用的問題(A引用B薪前,B引用A)
1.2 根搜索算法
通過一系列的成為GC Roots的點作為起始進行向下搜索润努,當(dāng)一個對象到GCRoots沒有任何引用鏈相連,則此對象時不可用的示括。
1.2.1 GC Roots
- 在vm棧中的引用
- 方法區(qū)的靜態(tài)引用
- jni(netive方法)中的引用
1.2.2 方法區(qū)的回收
商業(yè)化的虛擬機一般都會實現(xiàn)方法區(qū)的回收
- 回收內(nèi)容:
- 廢棄常量
- 無用類
- 回收滿足條件:
- 該類的實例都被回收
- 加載該類的類加載器被GC
- 該類的Class對象沒有在任何地方被引用铺浇,如不能在任何地方通過反射訪問到該類。
2.GC算法
2.1 標(biāo)記-清除算法(MS)
分為標(biāo)記和清除兩個階段垛膝,首先標(biāo)記所有需要回收的對象鳍侣,然后回收所有需要回收的對象。
缺點:
- 效率較低 標(biāo)記和清除的效率都比較低吼拥。需要掃描所有對象倚聚,堆越大越慢。
- 空間問題:回收后出現(xiàn)不連續(xù)空間扔罪,后續(xù)使用無法找到連續(xù)空間易造成再次回收秉沼。
2.2 標(biāo)記-整理算法(MC)
原理:標(biāo)記過程一樣,但后續(xù)步驟不是清除,而是令所有存活對象一端移動唬复,然后直接清理掉這端邊界以外的內(nèi)存矗积。
缺點:沒有內(nèi)存碎片,但移動(壓縮)對象更加耗費時間敞咧,
2.3 復(fù)制算法(Copying)
原理*
- 將可用內(nèi)存劃分為兩塊棘捣,每次只使用一塊,當(dāng)半塊內(nèi)存用完了休建,僅將還存活的對象復(fù)制到另外一塊上面乍恐,原有的一塊直接清除。
- 每次回收都是回收一半內(nèi)存测砂,也不用考慮內(nèi)存碎片問題茵烈。當(dāng)需要堆中內(nèi)存時,只需要移動堆指針砌些,按順序分配內(nèi)存呜投。
特點 - 這種算法是內(nèi)存會變?yōu)樵瓉硪话耄容^昂貴存璃。
- 在對象存活率較高的時候仑荐,效率有所下降。
- hotspot虛擬機默認eden和survivor的比例為8:1纵东,也就是每次有10%的空閑空間
2.4 分代算法(GN)
原理
- 當(dāng)前都是采用分代收集粘招,根據(jù)對象的不同的存活周期將內(nèi)存劃分為幾塊。
- 一般把堆內(nèi)存劃分為新生代和老年代偎球,這樣根據(jù)各個年代的不同特點采用不同的算法洒扎。
- 新生代每次cg都有大量對象死去,所以使用復(fù)制算法甜橱,只需復(fù)制少量存活的對象逊笆。
- 對于新生代栈戳,現(xiàn)代虛擬機使用這種算法:
過程:eden執(zhí)行復(fù)制算法岂傲,eden區(qū)滿后會gc,還存活的對象復(fù)制到s1 survivor,當(dāng)eden又滿時子檀,eden和s1 survivor中存活的對象將被復(fù)制到s2 survivor镊掖。如此循環(huán)。
如果對象的復(fù)制次數(shù)達到16次(默認)褂痰,該對象就會被送到老年代中亩进。
但是也不是固定的閾值,如果達到某個年齡發(fā)現(xiàn)總大小已經(jīng)大于survivor的50%,所以要動態(tài)調(diào)整閾值(否則會導(dǎo)致survivor空間不足)缩歪,讓這些存活的對象盡快晉升归薛。
TargetSurvivorRatio=60 ,值為一個百分比,當(dāng)對象占據(jù)survivor60時則調(diào)整閾值。
UseConcMarkSweepGC,老年代使用cms算法主籍。
UseParNewGC,新生代使用UseParNewGC算法习贫。 - 老年代:存放了經(jīng)過一次或多次GC還存活的對象
一般采用Mark-Sweep或者Mark-Compact算法
有多重垃圾回收器可以選擇,每種垃圾回收器可以看做一種GC算法的具體實現(xiàn)千元。根據(jù)具體需求選擇合適的垃圾回收器苫昌。 - 永久代:
并不屬于堆,但GC也會涉及這個地方幸海。
存放了每個Class的結(jié)構(gòu)信息祟身,包括常量池,字段描述物独,方法描述袜硫。與垃圾回收器要收集的java對象關(guān)系不大。
tops:引用類型
Hostpot將引用分為四種:
- strong:strong為new 出來的
- soft:內(nèi)存不夠時一定會被GC,長期不被引用也會被GC挡篓。
- weak:一定會被GC,當(dāng)被標(biāo)記為dead時父款,會在ReferenceQueue中通知。
- phantom:本來就沒引用瞻凤,當(dāng)從堆中釋放是會通知憨攒。
GC的時機
- Scavenge GC:
觸發(fā)時機:新對象生成,Eden滿了阀参。
理論上Eden區(qū)大多數(shù)對象會在Scavenge GC回收肝集,復(fù)制算法效率高,Scavenge GC時間短蛛壳。 - Full GC:
對整個jvm整理杏瞻,包括Young,Old 和 Perm
觸發(fā)時機:1 Old滿了,2 Perm 3.主動system.gc()衙荐。
效率很低捞挥,盡量減少Full GC。
3.垃圾回收器的實現(xiàn)和選擇
3.1 什么是垃圾回收器
- 分代模型:GC的愿景
- 垃圾回收器:GC的具體實現(xiàn)
- hotspot提供多種垃圾回收器忧吟,我們要根據(jù)具體應(yīng)用的需要采用不同的回收器砌函。
- 沒有萬能的垃圾回收器,每一種垃圾回收器都有自己使用的場景溜族。
3.2 垃圾回收器的并行和并發(fā)
- 并行:指多個收集器的線程同時工作讹俊,但是用戶線程處于等待狀態(tài)。
- 并發(fā):指收集器在工作的同時煌抒,可以允許用戶線程工作仍劈。
- 并發(fā)并不代表解決的gc停頓的問題,該停頓的步驟還是要停頓寡壮,比如標(biāo)記垃圾時贩疙。但清除垃圾時可以并發(fā)執(zhí)行讹弯。
3.3 垃圾回收器
1.Serial收集器
- 單線程收集器,收集時會暫停所有的工作線程这溅,使用復(fù)制收集算法闸婴,虛擬機運行在clent模式下默認新生代收集器。
- 在老年代采用標(biāo)記整理算法芍躏。
- 可以添加jvm參數(shù)UseSerialGC配合PretenureSizeThreshold參數(shù)改變閾值邪乍,使得超過某大小的對象直接在老年代創(chuàng)建。
2.Serial old收集器
- 單線程对竣,使用標(biāo)記整理算法庇楞,老年代收集器。
3.parnew收集器
- Serial的多線程版本否纬,除了多個收集線程外吕晌,其余行為包括算法,stw临燃,對象分配規(guī)則睛驳。回收策略等都與Serial收集器一樣膜廊。單cpu核心場景下乏沸,此收集器不會比Serial更好。
4.parallel scavenge收集器
- 是一個多線程收集器爪瓜,復(fù)制算法蹬跃。但他是以吞吐量最大(gc時間占總運行時間最小)為目標(biāo)的收集器铆铆。
Parallel Scavenge(-XX:+UseParallelGC)框架下蝶缀,默認是在要觸發(fā)full GC前先執(zhí)行一次young GC,并且兩次GC之間能讓應(yīng)用程序稍微運行一小下薄货,以期降低full GC的暫停時間(因為young GC會盡量清理了young gen的死對象翁都,減少了full GC的工作量)×禄控制這個行為的VM參數(shù)是-XX:+ScavengeBeforeFullGC柄慰。這是HotSpot VM里的奇葩。
5.parallel Old
- 老年代版本吞吐量收集器赊瞬,多線程先煎,標(biāo)記整理算法。
MaxTenuringThreshold=5 進入到老年代的最大存活年齡
+PrintTenuringDistribution
6.cms
- 標(biāo)記清除算法 Concurrent Mark Sweep
- cms是一種以最短停頓時間為目標(biāo)的收集器巧涧,使用cms并不能達到gc效率最高,但他能盡可能的降低gc時服務(wù)的停頓時間遥倦。
- 步驟:
- 初始標(biāo)記:標(biāo)記gcroots關(guān)聯(lián)的引用谤绳,快速占锯,暫停用戶線程。
- 并發(fā)標(biāo)記:gcroots關(guān)聯(lián)的引用往下接著標(biāo)記缩筛,不影響用戶線程消略。
- 并發(fā)預(yù)先清理:
- 并發(fā)可能失敗預(yù)清理:
- 重新標(biāo)記:修正并發(fā)標(biāo)記時變動的引用,較快速瞎抛,暫停用戶線程艺演。
- 并發(fā)清除:清除垃圾,不影響用戶線程桐臊。
- 并發(fā)重置:線程重置
-
缺點:
- 依賴cpu資源胎撤,處理時間短,但處理次數(shù)多断凶。
- 無法處理浮動垃圾伤提,有時虛擬機會使用備案,使用Serial Old收集器來重新進行老年代的垃圾回收认烁。
- 空間碎片問題肿男,
空間分配擔(dān)保:
在minor gc之前,虛擬機會檢查老年代最大可用連續(xù)空間是否大于新生代所有對象總空間却嗡。當(dāng)條件成立舶沛,在minorgc之后大量對象如果還會存活,則需要老年代進行空間分配擔(dān)保窗价,把survivor無法容納的對象直接進入老年代冠王。如果老年代判斷剩余空間不足(根據(jù)每一次回收晉升到老年代對象容量的平局值作為經(jīng)驗),則進行full gc舌镶。
4. 內(nèi)存泄漏及原因
- 對象定義在錯誤的范圍
- 異常處理不當(dāng)
- 集合數(shù)據(jù)管理不當(dāng)
5. 安全點理論
枚舉根節(jié)點:知道所有的根節(jié)點
hotspot使用一組成為OopMap的數(shù)據(jù)結(jié)構(gòu)來達到這個目的柱彻。安全點:
- hotspot并沒有為每條指令都生成OopMap,而只是在特定位置記錄了這些信息餐胀,這些位置稱為安全點哟楷,即程序執(zhí)行時并非在所有地方都能停下來gc,而是在安全點才能gc.
- 安全點的選定不能太少也不能太多,選定安全點的標(biāo)準是:“是否具有讓程序長時間執(zhí)行的特征”否灾,例如方法調(diào)用卖擅,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)墨技。
- 對于safepoint,另一個需要考慮的是如何在gc時惩阶,讓所有的線程都跑跑到最近的安全點停下來。
搶占式中斷:gc時先中斷扣汪,若有線程沒在安全點断楷,則恢復(fù)線程跑到安全點上。(此方式已被淘汰)
主動式中斷:如果要gc,虛擬機設(shè)置一個標(biāo)志崭别,各個線程輪詢這個標(biāo)志冬筒,發(fā)現(xiàn)標(biāo)志為真時就中斷掛起恐锣。輪詢的地方和安全點是重合的,另外在加上創(chuàng)建對象需要分配內(nèi)存的地方舞痰。
- 安全區(qū)域:
- 安全點也有解決不了的情況土榴,當(dāng)線程休眠,沒有給線程分配cpu時間响牛,線程無法響應(yīng)jvm中斷請求玷禽,jvm也不太可能等待休眠結(jié)束。此時需要安全區(qū)域解決問題呀打。
- 線程執(zhí)行到安全區(qū)域矢赁,首先標(biāo)識自己進入了安全區(qū)域,jvm在gc時不用管在安全區(qū)域的線程聚磺。當(dāng)線程要離開安全區(qū)域是坯台,要檢查jvm是否完成了根節(jié)點枚舉或整個gc,如果完成則繼續(xù)執(zhí)行,否則等待gc完成瘫寝。
6. G1垃圾回收器
G1垃圾回收器是一種兼顧吞吐量和響應(yīng)時間的垃圾回收器
G1回收器的堆結(jié)構(gòu)和傳統(tǒng)的堆結(jié)構(gòu)有很大不同蜒蕾,傳統(tǒng)堆結(jié)構(gòu)各區(qū)域是大塊分開,G1回收器堆內(nèi)存的物理結(jié)構(gòu)是無序的焕阿,堆空間eden,survivor,old區(qū)域是交織在一起的咪啡,并且區(qū)域的分類是會變化的。
6.1 G1收集器的內(nèi)存結(jié)構(gòu)
- 將堆劃分為大小相等的區(qū)域(regions),每個regions都有一個分代的角色暮屡。
- 對每個角色的數(shù)量并沒有強制限定撤摸,也就是說對每種分代內(nèi)存的總大小,可以動態(tài)變化褒纲。
- G1可以高效執(zhí)行回收准夷,優(yōu)先執(zhí)行那些大量對象可回收的區(qū)域。
- 使用個停頓可預(yù)測模型莺掠,根據(jù)用戶設(shè)定的停頓時間,g1會選擇哪些region要清除衫嵌,一次清除多少個。
- g1從多個region中回收彻秆,放入一個regin中(復(fù)制算法)楔绞。
因為會根據(jù)用戶設(shè)定的停頓時長gc,每次只收集少數(shù)的region唇兑,做到了間隔時間少酒朵,且使用copy算法,內(nèi)存碎片少扎附。
6.2 幾個概念
- 分區(qū):將整個堆分為大小相同的分區(qū)
- 原有的eden,survivor,old屬于不同的塊
- eden,survivor,old只是一種邏輯概念蔫耽。
- 優(yōu)先回收垃圾最多區(qū)域(region)
- 收集集合(cset):一組可被回收的分區(qū)的集合
- 存活的數(shù)據(jù)會被移動到另一個空閑分區(qū)
- cset中的分區(qū)可以來自eden,survivor,old。
- 已記憶集合(rset):rset記錄了其他region中對象引用本region中對象的關(guān)系帕棉。
- gc時不必掃描整個堆针肥,而是掃描rset即可饼记。即為減少掃描
- 因為eden區(qū)gc時要全部掃描香伴,所以只需記錄老年代到新生代的引用即可慰枕。
rset的兩個作用
RSet究竟是怎么輔助GC的呢?在做YGC的時候即纲,只需要選定young generation region的RSet作為根集具帮,這些RSet記錄了old->young的跨代引用,避免了掃描整個old generation低斋。 而mixed gc的時候蜂厅,old generation中記錄了old->old的RSet,young->old的引用由掃描全部young generation region得到膊畴,這樣也不用掃描全部old generation region掘猿。所以RSet的引入大大減少了GC的工作量。
標(biāo)記出RootRegion指向O區(qū)的region唇跨,標(biāo)記這些region是為了降低并發(fā)標(biāo)記的掃描范圍稠通,因為并發(fā)標(biāo)記需要掃描GCROOT引用或間接的所有對象,而這些對象一定是在RootRegion出發(fā)指向的Region中的买猖。MIXGC中Y區(qū)本來就要全掃改橘,所以這里再按照O區(qū)過濾下,這樣就縮小了掃描范圍玉控。該階段的操作為遍歷O區(qū)region查詢Rset是否有來自RootRegion的飞主,(RootRegion是初始標(biāo)記得到的)。
- STAB:
- 在并發(fā)標(biāo)記階段對快照標(biāo)記
- stab是g1 gc在并發(fā)標(biāo)記階段使用的增量式的標(biāo)記算法高诺。
- 并發(fā)標(biāo)記時并發(fā)多線程的碌识,但并發(fā)線程在同一時刻只掃描一個區(qū)。
拓展
card page:
通常將堆空間劃分為一系列2次冪大小的卡頁.
card table:
一card張表虱而,將region劃分為小區(qū)域在這張表上筏餐。
points-out:
指明我引用誰
points-into:
誰引用我
6.3 gc模式
- YongGC:選定所有年輕代的region,通過控制年輕代region薛窥,來控制gc的時間
- 觸發(fā)時機:eden區(qū)滿了時
- gc步驟:
- 根掃描:靜態(tài)和棧引用
- 更新rs:處理dirty card隊列更新rs
- 處理rs:檢測從年輕代指向老年代的對象
- 對象拷貝:拷貝存活對象
- 處理引用隊列:軟弱虛引用處理
- MixedGC:選定所有年輕代regin,外加根據(jù)global concurrent marking 統(tǒng)計出的若干高收益的老年代region胖烛,在用戶指定的stw時間內(nèi)盡可能的選擇收益高的region。
- 觸發(fā)時機:由一些參數(shù)控制诅迷,另外也控制著那些老年代regin會被選入cset
- global concurrent marking:全局并發(fā)標(biāo)記
執(zhí)行過程類似于cms,但主要為MixedGC提供標(biāo)記服務(wù)佩番,并不是gc中的必須環(huán)節(jié)。
步驟:
- 初始標(biāo)記:標(biāo)記了從GCroot開始可以直接可達的對象罢杉。(在YongGC中進行趟畏,伴隨著YongGC)
- 并發(fā)標(biāo)記:GCroot對heap中的對象進行標(biāo)記,標(biāo)記線程與用戶線程并發(fā)進行滩租,收集region的存活對象信息
- 重新標(biāo)記:標(biāo)記在并發(fā)階段變化的對象
- 清理:清空沒有存活對象的region,將region加入到free list.
- 三色標(biāo)記法:STAB中的算法
對象分為三種類型
- 黑色:根對象赋秀,或者該對象與他的子對象都被掃描過了(對象被標(biāo)記了利朵,且他的所有fieldd都被標(biāo)記完了)
- 灰色:對象本身被掃描,但還沒掃描完該對象中的子對象(field還沒有被標(biāo)記)
- 白色:未被掃描對象猎莲,掃描完成所有對象后最終為白色的為不可達對象绍弟,即垃圾對象。(對象沒有被標(biāo)記到)
satb步驟
- 開始標(biāo)記時生成快照圖
- 在并發(fā)標(biāo)記階段所有被改變的對象入隊(若在此階段改變引用著洼,將原有引用指向的對象變?yōu)榉前祝┱燎玻饕欠乐够厥辗抢鴮ο蟆?br> 在并發(fā)標(biāo)記階段可能產(chǎn)生漏標(biāo)記,所以對于gray灰對象一處的引用標(biāo)記為灰色身笤,對于black引用新增加的引用標(biāo)記為黑色豹悬。
- 可能存在浮動垃圾,將在下次護手
漏標(biāo)記情況舉例
漏標(biāo)只會發(fā)生在白色對象中液荸,需滿足以下兩個條件瞻佛。
- 并發(fā)標(biāo)記時,應(yīng)用線程給一個黑色對象的引用賦值了白色對象
- 并發(fā)標(biāo)記時娇钱,應(yīng)用線程刪除所有灰色對象到該白色對象的引用
掃描全部的老年代對象伤柄,是因為ygc的時候澈缺,老年代的對象全都被看做gcroots了涉波,需要在年輕代找被老年代任何對象引用的對象颠区,這些對象都不應(yīng)被清理嗡官。所以必須掃描所有老年代蹲盘,進行你說的鏈式查找的過程饮寞。而如果有了rset就知道有些old區(qū)region中根本沒有對年輕代對象的引用剖毯,直接就可以跳過這個region了旺坠。
[參考]
深入理解JVM 虛擬機
垃圾收集器|g1收集器