【深入理解Java虛擬機(jī)讀書(shū)筆記】垃圾收集器與內(nèi)存分配策略

垃圾收集器與內(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ū)中類靜態(tài)變量引用的對(duì)象
      • 方法區(qū)中常量引用的對(duì)象
  • 引用類型
    • 強(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線程去執(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í),它將被移出即將回收集合
  • 無(wú)用類
    • 該類的所有實(shí)例都被回收,也就是堆中不存在該類的任何實(shí)例
    • 加載該類的ClassLoader已經(jīng)被回收
    • 該類對(duì)應(yīng)的Class對(duì)象沒(méi)有在任何地方唄引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類

垃圾收集算法

  • 標(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)
  • 讓線程在安全點(diǎn)上停頓的方法
    • 搶先式中斷
      • GC發(fā)生時(shí),把所有線程全部中斷,如果發(fā)現(xiàn)線程中斷的地方不在安全點(diǎn)上,就恢復(fù)線程,讓其運(yùn)行至安全點(diǎn),幾乎沒(méi)有虛擬機(jī)采用這種方式
    • 主動(dòng)式中斷
      • 當(dāng)GC需要中斷線程時(shí),僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線程在執(zhí)行時(shí)主動(dòng)地輪詢?cè)摌?biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起,輪詢中斷的地方和安全點(diǎn)是重合的
  • 安全區(qū)域
    • 在一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)區(qū)域中的任意地方開(kāi)始GC都是安全的
    • 線程執(zhí)行到安全區(qū)域時(shí),首先標(biāo)志自己已經(jīng)進(jìn)入安全區(qū)域,此時(shí),當(dāng)JVM發(fā)起GC時(shí),就不需要管將自己標(biāo)志為安全區(qū)域的線程了,線程要離開(kāi)安全區(qū)域時(shí),先檢查系統(tǒng)是否完成了根節(jié)點(diǎn)枚舉(或者整個(gè)GC過(guò)程),如果完成,則繼續(xù)離開(kāi),否則,等待到可以安全離開(kāi)安全區(qū)域的信號(hào)為止

垃圾收集器

  • Serial收集器
    • 單線程收集器,在進(jìn)行垃圾收集時(shí),必須暫停所有的工作現(xiàn)場(chǎng),直到收集結(jié)束
    • 新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法
    • 虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器
    • 簡(jiǎn)單,高效
  • ParNew收集器
    • Serial收集器的多線程版本,使用多線程進(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收集器的老年代版本,單線程,使用標(biāo)記-整理算法
    • 主要給Client模式下的虛擬機(jī)使用
  • Parallel Old收集器
    • 多線程,采用標(biāo)記整理算法
    • 注重吞吐量以及CPU資源敏感的場(chǎng)合
  • CMS收集器
    • CMS(Concurrent Mark Sweep)以獲得最短回收停頓時(shí)間為目標(biāo)
    • 采用標(biāo)記-清除算法
    • 運(yùn)行過(guò)程
      1. 初始標(biāo)記,需要Stop The World,僅僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度快
      2. 并發(fā)標(biāo)記,對(duì)GC Roots Trancing的過(guò)程
      3. 重新標(biāo)記,需要Stop The World,修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蛲V惯\(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,比初始化標(biāo)記時(shí)間長(zhǎng),但比并發(fā)標(biāo)記時(shí)間短
      4. 并發(fā)清除
    • 并發(fā)標(biāo)記以及并發(fā)清除過(guò)程可以與用戶線程一起并發(fā)執(zhí)行
    • 缺點(diǎn)
      • 對(duì)CPU資源非常敏感,一定數(shù)量的線程用戶回收,從而使得用戶線程數(shù)量比例降低
      • 無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)Concurrent Mode Failure失敗而導(dǎo)致另一次Full GC的產(chǎn)生
        • 浮動(dòng)垃圾:在并發(fā)標(biāo)記過(guò)程中,由于標(biāo)記線程與用戶線程共同運(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)可以獲得盡可能高的收集效率
    • 內(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)了本地線程分配緩存(LTAB),則按照線程優(yōu)先在TLAB上分配,少數(shù)情況下也直接在老年代中分配

普遍的內(nèi)存分配策略

  • 對(duì)象優(yōu)先在Eden分配,如果空間不夠,將觸發(fā)一次Minor GC
  • 大對(duì)象直接進(jìn)入老年區(qū)
  • 長(zhǎng)期存活的對(duì)象將直接進(jìn)入老年代
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容