垃圾收集器與內(nèi)存分配策略
垃圾收集器主要回收的內(nèi)存區(qū)域是堆和方法區(qū)
判斷對(duì)象是否已死
- 引用計(jì)數(shù)算法
- 通過(guò)計(jì)算一個(gè)對(duì)象是否被其他對(duì)象所引用來(lái)判斷該對(duì)象是否可以被回收什湘,Java中不采用該方法贩据,存在循環(huán)引用問(wèn)題(a->b, b->a剔宪,此時(shí)a斗锭,b均不會(huì)被回收)
- 可達(dá)性分析算法
- 從一系列的
GC Root
出發(fā)胞得,如果一個(gè)對(duì)象沒(méi)有任何從引用鏈與GC Root相連接,則該對(duì)象可以被回收 - Java中的GC Root對(duì)象
- 虛擬機(jī)棧中本地變量表中引用的對(duì)象
- 本地方法棧中JNI(也就是Native方法)引用的對(duì)象
- 方法區(qū)中類(lèi)靜態(tài)變量引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 從一系列的
- 引用類(lèi)型
- 強(qiáng)引用,永遠(yuǎn)不會(huì)被回收
- 軟引用骗炉,有用但不是必須的對(duì)象诗赌,在系統(tǒng)即將要發(fā)生內(nèi)存溢出異常之前,會(huì)對(duì)其進(jìn)行二次回收
- 弱引用颓影,比軟應(yīng)用更弱,只能生存到下一次垃圾收集發(fā)生之前魏颓,垃圾收集器工作時(shí)驼壶,會(huì)回收
- 虛引用,最弱的一種引用碟渺,無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象
- 對(duì)象死亡過(guò)程
- 兩次標(biāo)記
- 如果對(duì)象在進(jìn)行可達(dá)性分析之后芜繁,發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,則會(huì)被第一次標(biāo)記并且執(zhí)行一次篩選高蜂,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法捺宗,當(dāng)對(duì)象沒(méi)有覆蓋finalize方法,或者finalize方法已經(jīng)被虛擬機(jī)調(diào)用過(guò)胶果,則將這兩種情況都視為沒(méi)有必要執(zhí)行
- 如果對(duì)象有必要執(zhí)行finalize方法匾嘱,則對(duì)象會(huì)被放置在一個(gè)叫F-Queue的隊(duì)列中,并且在稍后由一個(gè)虛擬機(jī)自行建立早抠、低優(yōu)先級(jí)的Finalizer線(xiàn)程去執(zhí)行
- finalize方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)霎烙,稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,如果對(duì)象在finalize方法中成功拯救自己蕊连,也就是重新與引用鏈上的任意對(duì)象建立關(guān)聯(lián)悬垃,則在第二次標(biāo)記時(shí),它將被移出即將回收集合
- 兩次標(biāo)記
- 無(wú)用類(lèi)
- 該類(lèi)的所有實(shí)例都被回收甘苍,也就是堆中不存在該類(lèi)的任何實(shí)例
- 加載該類(lèi)的ClassLoader已經(jīng)被回收
- 該類(lèi)對(duì)應(yīng)的Class對(duì)象沒(méi)有在任何地方唄引用尝蠕,無(wú)法在任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)
垃圾收集算法
- 標(biāo)記-清除算法(Mark-Sweep)
- 分為兩個(gè)階段,標(biāo)記载庭、清除
- 缺點(diǎn)
- 標(biāo)記以及清除過(guò)程的效率不高
- 標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片看彼,導(dǎo)致無(wú)法為大對(duì)象分配空間,從而導(dǎo)致觸發(fā)另一次垃圾收集算法
- 復(fù)制算法(Copying)
- 將可用內(nèi)存按容量分為大小相等的兩塊囚聚,每次只使用其中一塊靖榕,當(dāng)這一塊使用完之后,就將還存活著的對(duì)象復(fù)制到另一塊內(nèi)存顽铸,然后一次性清理已使用過(guò)的內(nèi)存空間
- 缺點(diǎn)
- 導(dǎo)致每次可用內(nèi)存大小縮小為原來(lái)的一半
- 在對(duì)象存活比較多時(shí)需要進(jìn)行比較多的復(fù)制操作
- 現(xiàn)在商業(yè)虛擬機(jī)都采用這種收集算法來(lái)回收新生代(朝生夕死)茁计,將內(nèi)存分為較大的Eden空間和兩個(gè)較小的Survivor空間,每次使用Eden和其中一塊Survicor空間谓松,回收時(shí)星压,將存活對(duì)象復(fù)制到另一個(gè)Survicor空間践剂,最后清理Eden和剛剛使用過(guò)的Survicor空間,HotSpot中默認(rèn)Eden:Survivor = 8:1娜膘,也就是新生代中每次可用內(nèi)存為90%逊脯,當(dāng)Survivor空間不足時(shí),需要老年代進(jìn)行分配擔(dān)保
- 當(dāng)另一塊Survivor空間不足以存放上一次新生代存活下來(lái)的對(duì)象時(shí)劲绪,通過(guò)分配擔(dān)保機(jī)制直接進(jìn)入老年代
- 標(biāo)記-整理算法(Mark-Compact)
- 分為兩個(gè)階段男窟,標(biāo)記、整理贾富,讓存活的對(duì)象向一端移動(dòng)歉眷,然后直接清除端邊界以外的內(nèi)存,主要用于老年代
- 分代收集算法(Generation Collection)
- 商業(yè)虛擬機(jī)主要采用方式颤枪,根據(jù)對(duì)象存活周期的不同汗捡,將內(nèi)存劃分為幾塊,一般把堆分為新生代和老年代畏纲,然后根據(jù)各個(gè)年代的特點(diǎn)扇住,采用最適當(dāng)?shù)氖占惴?/li>
- 新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去盗胀,只有少量存活艘蹋,就選用復(fù)制算法
- 老年代中,對(duì)象存活率高票灰,沒(méi)有額外的空間對(duì)它進(jìn)行分配擔(dān)保女阀,一般采用標(biāo)記清除或者標(biāo)記整理算法
HotSpot的算法實(shí)現(xiàn)
- 程序在執(zhí)行時(shí),并非在所有的地方都能停頓下來(lái)開(kāi)始GC屑迂,只有在到達(dá)安全點(diǎn)時(shí)才暫停
- 安全點(diǎn)的選定基本上是以程序"是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征"為標(biāo)準(zhǔn)進(jìn)行選定的浸策,長(zhǎng)時(shí)間特征為,指令序列的復(fù)用惹盼,如方法調(diào)用庸汗,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)等手报,只有具有這些功能的指令才會(huì)產(chǎn)生安全點(diǎn)
- 讓線(xiàn)程在安全點(diǎn)上停頓的方法
- 搶先式中斷
- GC發(fā)生時(shí)蚯舱,把所有線(xiàn)程全部中斷,如果發(fā)現(xiàn)線(xiàn)程中斷的地方不在安全點(diǎn)上掩蛤,就恢復(fù)線(xiàn)程晓淀,讓其運(yùn)行至安全點(diǎn),幾乎沒(méi)有虛擬機(jī)采用這種方式
- 主動(dòng)式中斷
- 當(dāng)GC需要中斷線(xiàn)程時(shí)盏档,僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線(xiàn)程在執(zhí)行時(shí)主動(dòng)地輪詢(xún)?cè)摌?biāo)志燥爷,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起蜈亩,輪詢(xún)中斷的地方和安全點(diǎn)是重合的
- 搶先式中斷
- 安全區(qū)域
- 在一段代碼片段中懦窘,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)區(qū)域中的任意地方開(kāi)始GC都是安全的
- 線(xiàn)程執(zhí)行到安全區(qū)域時(shí)稚配,首先標(biāo)志自己已經(jīng)進(jìn)入安全區(qū)域畅涂,此時(shí),當(dāng)JVM發(fā)起GC時(shí)道川,就不需要管將自己標(biāo)志為安全區(qū)域的線(xiàn)程了午衰,線(xiàn)程要離開(kāi)安全區(qū)域時(shí),先檢查系統(tǒng)是否完成了根節(jié)點(diǎn)枚舉(或者整個(gè)GC過(guò)程)冒萄,如果完成臊岸,則繼續(xù)離開(kāi),否則尊流,等待到可以安全離開(kāi)安全區(qū)域的信號(hào)為止
垃圾收集器
- Serial收集器
- 單線(xiàn)程收集器帅戒,在進(jìn)行垃圾收集時(shí),必須暫停所有的工作現(xiàn)場(chǎng)崖技,直到收集結(jié)束
- 新生代采用復(fù)制算法逻住,老年代采用標(biāo)記-整理算法
- 虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器
- 簡(jiǎn)單,高效
- ParNew收集器
- Serial收集器的多線(xiàn)程版本迎献,使用多線(xiàn)程進(jìn)行垃圾收集瞎访,其余基本同Serial
- 新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法
- 運(yùn)行在Server模式下首選的新生代收集器吁恍,除了Serial外扒秸,只有ParNew能與CMS收集器配合工作
- Parallel Scavenge收集器
- 并行的采用復(fù)制算法的新生代收集器
- 目標(biāo)是達(dá)到一個(gè)可控制的吞吐量,吞吐量?jī)?yōu)先收集器
- Serial Old收集器
- Serial收集器的老年代版本践盼,單線(xiàn)程鸦采,使用標(biāo)記-整理算法
- 主要給Client模式下的虛擬機(jī)使用
- Parallel Old收集器
- 多線(xiàn)程,采用標(biāo)記整理算法
- 注重吞吐量以及CPU資源敏感的場(chǎng)合
- CMS收集器
- CMS(Concurrent Mark Sweep)以獲得最短回收停頓時(shí)間為目標(biāo)
- 采用標(biāo)記-清除算法
- 運(yùn)行過(guò)程
- 初始標(biāo)記咕幻,需要Stop The World渔伯,僅僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度快
- 并發(fā)標(biāo)記肄程,對(duì)GC Roots Trancing的過(guò)程
- 重新標(biāo)記锣吼,需要Stop The World,修正并發(fā)標(biāo)記期間因?yàn)橛脩?hù)程序停止運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄蓝厌,比初始化標(biāo)記時(shí)間長(zhǎng)玄叠,但比并發(fā)標(biāo)記時(shí)間短
- 并發(fā)清除
- 并發(fā)標(biāo)記以及并發(fā)清除過(guò)程可以與用戶(hù)線(xiàn)程一起并發(fā)執(zhí)行
- 缺點(diǎn)
- 對(duì)CPU資源非常敏感,一定數(shù)量的線(xiàn)程用戶(hù)回收拓提,從而使得用戶(hù)線(xiàn)程數(shù)量比例降低
- 無(wú)法處理浮動(dòng)垃圾读恃,可能出現(xiàn)Concurrent Mode Failure失敗而導(dǎo)致另一次Full GC的產(chǎn)生
- 浮動(dòng)垃圾:在并發(fā)標(biāo)記過(guò)程中,由于標(biāo)記線(xiàn)程與用戶(hù)線(xiàn)程共同運(yùn)行,所以可能給還會(huì)產(chǎn)生新的垃圾寺惫,而這些垃圾在本次手機(jī)過(guò)程無(wú)法被回收
- 由于是基于標(biāo)記-清除算法疹吃,會(huì)產(chǎn)生許多的內(nèi)存碎片,當(dāng)碎片過(guò)多時(shí)西雀,會(huì)給大對(duì)象分配帶來(lái)麻煩萨驶,從而觸發(fā)一次Full GC,內(nèi)存整理過(guò)程無(wú)法并發(fā)艇肴,所以會(huì)導(dǎo)致停頓時(shí)間變長(zhǎng)
- G1收集器
- Garbage-First收集器腔呜,面向服務(wù)器端應(yīng)用的垃圾收集器,主要同于替換CMS收集器
- 特點(diǎn)
- 并行與并發(fā)
- 充分利用多CPU再悼,多核環(huán)境核畴,使用多個(gè)CPU來(lái)縮短Stop-The-World停頓的時(shí)間
- 分代收集
- 空間整合
- 整體采用標(biāo)記-整理算法實(shí)現(xiàn),局部采用復(fù)制算法
- 可預(yù)測(cè)的停頓
- 除了降低停頓外帮哈,還能建立可預(yù)測(cè)的停頓時(shí)間模型
- 可以有計(jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集膛檀,跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收獲得的空間以及回收所需要的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先隊(duì)列娘侍,每次根據(jù)允許的收集時(shí)間咖刃,優(yōu)先回收價(jià)值最大的Region,保證在有限時(shí)間內(nèi)可以獲得盡可能高的收集效率
- 并行與并發(fā)
- 內(nèi)存布局
- 將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region憾筏,雖然保留新生代嚎杨,老年代的概念,但是新生代氧腰,老年代不再是物理隔離枫浙,都是一部分Region的集合
內(nèi)存分配
對(duì)象的內(nèi)存分配,主要是在堆上進(jìn)行分配古拴,也有可能經(jīng)過(guò)JIT編譯之后被拆散為標(biāo)量并間接在棧上分配箩帚,對(duì)象主要分配在新生代的Eden區(qū),如果啟動(dòng)了本地線(xiàn)程分配緩存(LTAB)黄痪,則按照線(xiàn)程優(yōu)先在TLAB上分配紧帕,少數(shù)情況下也直接在老年代中分配
普遍的內(nèi)存分配策略
- 對(duì)象優(yōu)先在Eden分配,如果空間不夠桅打,將觸發(fā)一次Minor GC
- 大對(duì)象直接進(jìn)入老年區(qū)
- 長(zhǎng)期存活的對(duì)象將直接進(jìn)入老年代