原文:為知筆記外鏈
對(duì)象回收設(shè)計(jì)兩類操作:
- 判斷對(duì)象是否可回收
- 執(zhí)行回收
判斷對(duì)象可被回收的算法:
引用計(jì)數(shù)法 【效率高滑燃,無法解決相互引用問題】
給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有地方引用則該計(jì)數(shù)器值+1溯警,當(dāng)引用失效則-1;計(jì)數(shù)器為0的對(duì)象不可再被使用甲锡,可以被回收拒名。
可達(dá)性分析算法
通過一系列成為 “GC Root" 的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索锄奢,搜索所走過的路徑為 引用鏈(MAT中的支配樹)失晴,當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連,即GC Roots到這個(gè)對(duì)象不可達(dá)時(shí)拘央,則證明此對(duì)象不可用涂屁。
可作為GC Roots的對(duì)象包括:
虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。
方法區(qū)中類靜態(tài)屬性引用的對(duì)象灰伟。
方法區(qū)中常量引用的對(duì)象拆又。
本地方法棧中JNI(Native方法)引用的對(duì)象。
引用:
強(qiáng)引用:GC永遠(yuǎn)不會(huì)回收掉的被引用的對(duì)象【通過 new 創(chuàng)建的對(duì)象】
軟引用【SoftReference】:有用但非必須的對(duì)象栏账。在系統(tǒng)將要發(fā)生內(nèi)存溢出之前帖族,將會(huì)把這些對(duì)象列進(jìn)回收范圍中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存挡爵,才會(huì)拋出內(nèi)存溢出異常竖般。
弱引用【W(wǎng)eakReference】:非必須的對(duì)象。強(qiáng)度比軟引用弱茶鹃,被弱引用關(guān)聯(lián)的對(duì)象只能存活到下次垃圾收集發(fā)生之前涣雕。當(dāng)垃圾回收時(shí),無論當(dāng)前內(nèi)存是否足夠闭翩,都會(huì)回收掉被弱引用關(guān)聯(lián)的對(duì)象挣郭。
虛引用【PhantomReference】:無用,唯一目的就是能在被收集器回收時(shí)受到一個(gè)系統(tǒng)通知疗韵。
即使在可達(dá)性分析算法中不可達(dá)的對(duì)象兑障,也并非是“非死不可”的,這時(shí)它們會(huì)處于 ”緩刑“ 階段,要真正宣告一個(gè)對(duì)象死亡流译,至少要經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析之后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈逞怨,那它將被第一次標(biāo)記并且進(jìn)行一次 篩選 ,篩選的條件是此對(duì)象是否有必要執(zhí)行 finalize() 方法先蒋『眨【當(dāng)對(duì)象沒有覆蓋 finalize() 方法,或者 finalize() 方法已被虛擬機(jī)調(diào)用過竞漾,jvm將這兩種情況視為 沒有必要執(zhí)行 】眯搭。
如果該對(duì)象被判定為有必要執(zhí)行 finalize() 方法,那么該對(duì)象會(huì)被放置在一個(gè) F-Queue 的隊(duì)列中业岁,并在稍后由一個(gè)虛擬機(jī)自動(dòng)建立的鳞仙、低優(yōu)先級(jí)的 Finalizer 線程去執(zhí)行它”适保【所謂執(zhí)行是指jvm會(huì)觸發(fā)該方法棍好,但并不承諾會(huì)等待它運(yùn)行結(jié)束,因?yàn)槿绻粋€(gè)對(duì)象在 finalize() 方法中執(zhí)行緩慢或死循環(huán)允耿,將可能導(dǎo)致F-Queue隊(duì)列中其他對(duì)象永久處于等待借笙,甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰〗衔】
finalize() 方法時(shí)對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)业稼,稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,如果對(duì)象要在finalize()方法中成功拯救自己--只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可蚂蕴,之后在第二次標(biāo)記時(shí)會(huì)被移除“即將回收”的集合低散。
如果對(duì)象這時(shí)還沒有逃脫,基本上就真的會(huì)被回收了【u are done】骡楼。
任何一個(gè)對(duì)象的finalize()方法都只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次熔号,如果對(duì)象面臨下一次回收,則該方法不會(huì)再被執(zhí)行鸟整。
垃圾收集算法:【執(zhí)行回收】
標(biāo)記-清除算法
最基礎(chǔ)的收集算法引镊。算法分為 “標(biāo)記” 和 “清除” 兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象篮条〉芡罚【標(biāo)記過程如上】。
存在問題:
效率不高兑燥。標(biāo)記和清除效率都不高。
空間問題:標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片琴拧,空間碎片太多可能會(huì)導(dǎo)致以后程序運(yùn)行過程中需要分配較大對(duì)象時(shí)降瞳,無法找到足夠連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
復(fù)制算法【新生代】
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊挣饥。當(dāng)這一塊的內(nèi)存用完了除师,就將還存活著的對(duì)象復(fù)制到另外一塊上邊,然后再把已使用過的內(nèi)存空間一次清理掉扔枫。
好處:每次對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收汛聚,不必考慮內(nèi)存碎片等情況,只要移動(dòng)堆頂指針短荐,按順序分配內(nèi)存即可倚舀,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效忍宋。
不足:代價(jià)是將內(nèi)存縮小一半痕貌。
目前主流均采用該收集算法回收新生代【分代收集】。IBM研究表明糠排,新生代中的對(duì)象98%都是朝生夕死舵稠,所以不必按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存劃分為一塊兒比較大的 Eden空間和兩塊較小的Survivor空間入宦,每次使用 Eden 和其中一塊Survivor空間哺徊。當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性的復(fù)制到另外一塊Survivor空間上乾闰,最后清理掉Eden和剛剛使用過的Survivor空間落追。HotSpot虛擬機(jī)默認(rèn)比例是8:1,也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%)汹忠,只有10%的內(nèi)存會(huì)被“浪費(fèi)”
內(nèi)存的分配擔(dān)保機(jī)制:如果另外一塊兒Survivor空間沒有足夠空間存放上次新生代收集下來的存活的對(duì)象時(shí)淋硝,這些對(duì)象會(huì)通過 分配擔(dān)保機(jī)制 進(jìn)入老年代。
標(biāo)記-整理算法【老年代】
復(fù)制收集算法在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作宽菜,效率會(huì)變很低谣膳。如果不想浪費(fèi)50%的空間,就需要額外的空間進(jìn)行分配擔(dān)保铅乡,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況继谚,所以老年代一般不直接選用這種算法
標(biāo)記過程與 標(biāo)記-清除 算法一致,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理阵幸,而是讓所有存活的對(duì)象都向一端移動(dòng)花履,然后直接清理掉端邊界以外的內(nèi)存。示意圖如下:
分代收集算法
目前商業(yè)虛擬機(jī)都采用的垃圾收集算法挚赊。根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊诡壁。將Java堆分為新生代和老年代。這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ㄜ睢T谛律忻们洌看卫占瘯r(shí)都會(huì)有大批對(duì)象死去旺矾,只有少量存活,則選用復(fù)制算法夺克,只需付出少量存活對(duì)象的復(fù)制成本即可箕宙。而老年代中因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保铺纽,因此必須使用標(biāo)記-清理或標(biāo)記-整理算法進(jìn)行回收柬帕。
臭名昭著的 STW (Stop the world)
可達(dá)性分析時(shí),必須在一個(gè)能確保一致性的快照中進(jìn)行---即整個(gè)分析期間整個(gè)執(zhí)行系統(tǒng)看起來就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上狡门,不可以出現(xiàn)分析過程中對(duì)象引用關(guān)系還在不斷變化的情況陷寝,否則分析結(jié)果準(zhǔn)確性就無法保證。這點(diǎn)是導(dǎo)致GC進(jìn)行時(shí)必須停頓所有Java執(zhí)行線程的一個(gè)重要原因融撞。即使在號(hào)稱幾乎不會(huì)發(fā)生停頓的CMS收集器中盼铁,枚舉根節(jié)點(diǎn)時(shí)也必須停頓。
安全點(diǎn):但程序執(zhí)行時(shí)并非在所有的地方都能停頓下來開始GC尝偎,只有到達(dá)安全點(diǎn)(SafePoint)才能暫停饶火。讓所有線程在GC發(fā)生時(shí)跑到安全點(diǎn)的方法有:搶先式中斷和主動(dòng)式中斷。
內(nèi)存分配與回收策略
對(duì)象優(yōu)先在Eden分配致扯。大多數(shù)情況下肤寝,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí)抖僵,虛擬機(jī)將發(fā)起一次Minor GC.
大對(duì)象直接進(jìn)入老年代鲤看。需要大量連續(xù)內(nèi)存空間的Java對(duì)象,如很長(zhǎng)的字符串或數(shù)組耍群,將被分配到老年代义桂。
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代。虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器蹈垢。如果對(duì)象在Eden出生并且經(jīng)過依次Minor GC后仍然存活慷吊,并且能被Survivor容納,將被移動(dòng)到Survivor空間中曹抬,并且對(duì)象年齡設(shè)為1溉瓶。對(duì)象在Survivor區(qū)中每 “熬過” 依次Minor GC,年齡就會(huì)+1谤民,當(dāng)年齡增加到一定程度(默認(rèn)15堰酿,可通過 -XX:MaxTenuringThreshold設(shè)置),就會(huì)被晉升到老年代中张足。
如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半触创,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代,無須等到 MaxTenuringThreshold 中要求的年齡为牍。