判斷對(duì)象是否存活
可達(dá)性分析算法
通過(guò)一系列稱為"GC Roots"的對(duì)象作為起點(diǎn)哼转,從這些節(jié)點(diǎn)開(kāi)始向下搜索覆劈,搜索所有走過(guò)的路徑稱為引用鏈荔睹,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)(從GC Roots到此對(duì)象不可達(dá))揖盘,則證明此對(duì)象是不可用的楷力。
可作為GC Roots的對(duì)象包括:
- 虛擬機(jī)棧中所引用的對(duì)象
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中JNI引用的對(duì)象
引用的分類
- 強(qiáng)引用(Strong Reference): 在代碼中普遍存在的喊式,類似"Object obj = new Object()"這類引用,只要強(qiáng)引用還在萧朝,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
- 軟引用(Sofe Reference): 有用但并非必須的對(duì)象岔留,可用SoftReference類來(lái)實(shí)現(xiàn)軟引用,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前检柬,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行二次回收献联。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存異常異常何址。
- 弱引用(Weak Reference): 被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前枢步,JDK提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用
- 虛引用(Phantom Reference):也稱為幽靈引用或幻影引用灸撰,是最弱的一種引用關(guān)系,JDK提供了PhantomReference類來(lái)實(shí)現(xiàn)虛引用。
不要使用finalize()方法來(lái)挽救對(duì)象周崭。
JVM判定無(wú)用的類的條件:
- 該類的所有實(shí)例已經(jīng)被回收螺男,java堆中不存在該類的任何示例
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用
垃圾收集算法
標(biāo)記-清除算法
即先標(biāo)記所有需要回收的對(duì)象华望,在標(biāo)記完成后統(tǒng)一進(jìn)行回收丈秩。該算法的兩個(gè)不足:一個(gè)是效率問(wèn)題,標(biāo)記和清除兩個(gè)過(guò)程效率不高烁巫;另一個(gè)是空間問(wèn)題署隘,標(biāo)記清除后產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多導(dǎo)致為較大對(duì)象分配內(nèi)存時(shí)亚隙,找不到足夠大的連續(xù)內(nèi)存磁餐。
復(fù)制算法
將可用內(nèi)存按容量劃分為大小相對(duì)的兩塊,每次只使用其中一塊阿弃,當(dāng)這一塊內(nèi)存使用完诊霹,將還存活的對(duì)象復(fù)制到另一塊,然后將已使用過(guò)的內(nèi)存一次清理掉渣淳,很明顯這種方式雖然解決了內(nèi)存碎片問(wèn)題脾还,但是可用內(nèi)存縮小為原來(lái)的一半,太浪費(fèi)了∪肜ⅲ現(xiàn)在大部分的虛擬機(jī)都使用這種方式來(lái)回收新生代鄙漏,即Eden區(qū)與兩個(gè)Survivor區(qū)嗤谚,默認(rèn)Eden:一個(gè)Survivor=8:1
標(biāo)記-復(fù)制算法
標(biāo)記過(guò)程如標(biāo)記-清除算法一樣,后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理怔蚌,而是讓所有存活的對(duì)象都向一端移動(dòng)巩步,然后直接清理掉邊界以外的內(nèi)存。
垃圾收集器
這里主要列舉現(xiàn)在常用的收集器
CMS收集器
現(xiàn)在常用的一種垃圾收集器桦踊,基于標(biāo)記-清除算法椅野,收集過(guò)程包含4個(gè)步驟:
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 重新標(biāo)記
- 并發(fā)清除
其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”籍胯。初始標(biāo)記僅僅是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象竟闪,速度很快,并發(fā)標(biāo)記就是進(jìn)行GC Roots Trancing的過(guò)程芒炼,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間比初始標(biāo)記稍長(zhǎng)一些术徊,但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短本刽。
由于整個(gè)過(guò)程耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過(guò)程收集器線程都可以與用戶線程一起工作,所以從整體上看赠涮,CMS收集器的內(nèi)存回收過(guò)程是與用戶線程并發(fā)執(zhí)行的子寓。
CMS收集器的缺點(diǎn):
- 對(duì)CPU資源敏感 雖然不會(huì)導(dǎo)致用戶線程停頓,但會(huì)占用一部分線程導(dǎo)致應(yīng)用程序變慢笋除,總吞吐量會(huì)降低斜友。CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(CPU數(shù)量+3)/4
- 無(wú)法處理浮動(dòng)垃圾 可能出現(xiàn)"Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC的產(chǎn)生。浮動(dòng)垃圾是回收的過(guò)程與用戶線程并行時(shí)用戶線程產(chǎn)生的垃圾垃它。
- 產(chǎn)生內(nèi)存碎片 使用標(biāo)記-清除算法
G1收集器
一款面向服務(wù)端應(yīng)用的比較新的垃圾收集器鲜屏,具備以下特點(diǎn):
- 并行與并發(fā) G1能夠充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢(shì)国拇,使用多個(gè)CPU核心來(lái)縮短Stop The World的停頓時(shí)間
- 分代收集
- 空間整合 同時(shí)使用標(biāo)記-整理與復(fù)制算法不會(huì)產(chǎn)生內(nèi)存空間碎片
- 可預(yù)測(cè)的停頓 能讓使用者指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi)洛史,消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒
G1的垃圾收集步驟:
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 最終標(biāo)記
- 篩選回收
內(nèi)存分配與回收策略
-
對(duì)象優(yōu)先在Eden分配,當(dāng)Eden沒(méi)有足夠的空間時(shí)酱吝,虛擬機(jī)發(fā)起一次Minor GC也殖。可以通過(guò)JVM參數(shù):-XX:+PrintGCDetails打印GC日志查看垃圾收集情況务热。
示例:
JVM Args: -Xms20m -Xmx20m -Xmn10m
堆大小共20m忆嗜,其中新生代10m,老年代10m崎岂,默認(rèn)-XX:SurvivorRatio=8決定了新生代中Eden區(qū)與一個(gè)Survivor區(qū)的空間比例是8:1 大對(duì)象直接進(jìn)入老年代捆毫,如長(zhǎng)字符串及數(shù)組,jvm提供了一個(gè)-XX:PretenureSizeThreshold參數(shù)冲甘,令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配內(nèi)存冻璃,避免在Eden與Survivor之間發(fā)生大量?jī)?nèi)存復(fù)制响谓。
長(zhǎng)期存活的對(duì)象進(jìn)入老年代 虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器。如果對(duì)象在Eden出生并經(jīng)過(guò)一次Minor GC后仍然存活省艳,并且能被Survivor容納娘纷,將被移動(dòng)到Survivor空間,并且對(duì)象年齡設(shè)為1跋炕,對(duì)象在Survivor空間每熬過(guò)一次Minor GC赖晶,年齡就增加1歲,當(dāng)年齡到達(dá)一定長(zhǎng)度(默認(rèn)15)辐烂,將會(huì)被晉升都老年代遏插。如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代纠修,無(wú)須等到PretenureSizeThreshold中要求的年齡胳嘲。
空間分配擔(dān)保,在發(fā)生Minor GC之前扣草,虛擬機(jī)會(huì)先檢查老年代最大可用連續(xù)空間是否大于新生代所有對(duì)象總空間了牛,如果是則Minor GC是安全的。如果不是虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗辰妙。如果允許鹰祸,那么會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于密浑,將嘗試進(jìn)行一次Minor GC蛙婴,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn)尔破,那這是要進(jìn)行一次Full GC街图。因?yàn)樵跇O端的情況下,即新生代所有對(duì)象都存活懒构,就需要把Survivor無(wú)法容納的對(duì)象直接放入老年代台夺。
參考
《深入理解java虛擬機(jī) JVM高級(jí)特性與最佳實(shí)踐》