二、JVM垃圾收集

GC需要完成的三件事情

  • 哪些對象需要回收?
  • 什么時候回收?
  • 如何回收?

一、那些對象需要回收?(如何確定對象已死)

對象已死:不可能再被任何途徑使用的對象

1.引用計數(shù)法

引用計數(shù)法:給對象添加一個引用計數(shù)器,每當一個地方引用它,計數(shù)器就加一;當引用失效時,計數(shù)器就減一;任何時刻計數(shù)器為0的對象就是不可能再被使用的。
問題:難以解決對象間相互引用的問題,所以主流JVM里面都沒有選擇該方法;

2. 可達性分析

可達性分析:通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。

可達性分析算法判斷對象是否可回收.png

GC Roots包括

  • 虛擬機棧(棧中本地變量表)中引用的對象,即當前執(zhí)行方法中的對象
  • 方法區(qū)中靜態(tài)屬性引用的對象,即 使用static修飾的類屬性,所以可使用static修飾的容器放置程序執(zhí)行上下文一類的屬性
  • 方法區(qū)中常量引用的對象,即 使用final修飾的對象
  • 本地方法棧中JNI/Native方法引用的對象

二、如何回收?(垃圾收集算法)

1. 標記-清除算法

首先標記出所有需要被回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象

  • 問題:
    • 效率問題:標記和清除兩個過程的效率都不高;
    • 空間問題:標記清除后會產(chǎn)生大量的內(nèi)存碎片


      標記-清除算法.png
2. 復制算法

復制算法將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊內(nèi)存用完了,就將還存活著的對象復制到另一邊,然后再把已使用過的內(nèi)存空間一次清理掉

復制算法.png

HotSpot虛擬機默認Eden和Survivor的大小比例為8:1

3. 標記-整理算法

首先標記出所有需要被回收的對象,標記完成后讓所有存活的對象都向一端移動,然后清理掉端邊界以外的內(nèi)存

標記-整理算法.png

4. 分代收集算法

一般把Java堆分為新生代和老年代,這樣就可以根據(jù)各個代的特點采用最適合的收集算法

  • 新生代:使用復制算法收集,因為每次垃圾收集都會有大量的對象死去,只有少量存活,只需要付出少量存活對象的復制成本就可以完成收集;
  • 老年代: 使用標記-整理算法或標記-清除算法,因為老年代中因為對象存活率高,、沒有額外空間對它進行分配擔保;

三、HotSpot的算法實現(xiàn)

已可達性分析算法為例,第一步首先找到所有的根節(jié)點——遍歷根節(jié)點

1. 枚舉根節(jié)點
  • Stop the World:指在進行垃圾回收的時候,需要將所有正在執(zhí)行的Java線程全部停止,以保證分析的準確性;
  • 目前的主流JVM使用的都是準確式GC(即JVM可以知道內(nèi)存中某個位置具體是什么類型),所以JVM不需要遍歷所有的執(zhí)行上下文和全局引用(因為GC Roots主要存在與這兩個地方)的位置。
  • 在HotSpot實現(xiàn)中,是使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)達到準確式GC的目的
2. 安全點(如何完美進入GC)

因為對象引用變化的情況有點多,對象引用的變化同樣引起OopMap內(nèi)容的變化,因為OopMap是在特定的位置存儲這些信息的,所以如果每次OopMap變化都存儲,那將會需要大量的額外空間,這樣GC的成本會變得很高。所以HotSpot只有在“特定的位置”記錄這些信息,這些位置稱為安全點;(即程序在執(zhí)行時并非在所有地方都能停頓下來開始GC,只有到達安全點才能暫停)

  • 安全點選取標準:
    • 基本以程序“是否具有讓程序長時間執(zhí)行的特征”為標準進行選定
    • 因為每條指令執(zhí)行的時間都非常短暫,程序不太可能因為指令流長度太長這個原因過長時間運行,“長時間執(zhí)行”的最明顯特征就是指令序復用,如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等
  • 讓所有線程在安全點停頓
    • 搶占式中斷:首先把所有線程全部中斷,如果有發(fā)現(xiàn)線程中斷的地方不在安全點上,就恢復線程,讓其運行到安全點。(現(xiàn)在幾乎沒有JVM采用過這種方法)
    • 主動式中斷:當GC需要中斷時,不直接對線程操作,而是設(shè)置一個標志(告訴線程需要中斷的標志),各個線程在執(zhí)行時會主動輪詢這個標志,當為true時,自己就中斷掛起
3. 安全區(qū)域

當線程sleep或者阻塞狀態(tài)時,這時線程無法響應JVM的中斷響應,對于這種情況,就需要安全區(qū)域來解決

四、垃圾收集器

HotSpotJVM的垃圾收集器.png

雖然會對各個垃圾收集器比較,但是并不是要選出一個最好的收集器。因為到目前為止,還沒有
最好的垃圾收集器出現(xiàn)。

  • Serial收集器

    Serial_Serial Old收集器運行示意圖.png

    • 新生代收集器
    • 使用復制算法
    • 單線程的收集器
    • 在進行垃圾收集時,必須暫停其他所有的工作線程,直到收集結(jié)束(stop the world)
    • 優(yōu)點:簡單高效(與其他收集器相比),對于單CPU環(huán)境能獲得更好的效率
  • ParNew收集器

    ParNew_Serial Old收集器運行示意圖.png

    • 新生代收集器
    • 使用復制算法
    • Serial收集器的多線程版本,除使用多條線程進行垃圾收集之外其余都和Serial收集器一樣
    • 是許多運行在Server模式下的虛擬機首選新生代收集器,主要原因是除Serial外只有它可以和CMS搭配使用
  • Parallel Scavenge收集器(示意圖如Paraller Old)

    • 新生代收集器
    • 使用復制算法
    • 關(guān)注點與其他收集器不同,目標為達到一個可控制的吞吐量(吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間))
    • 提供-XX:MaxGCPauseMillis(控制最大垃圾收集停頓時間)和-XX:GCTimeRatio(直接設(shè)置吞吐量大小)
    • 提供-XX:+UseAdptiveSizePolicy動態(tài)根據(jù)JVM運行情況調(diào)節(jié)新生代及老年代的內(nèi)存分配
  • Serial Old收集器(示意圖如Serial)

    • 老年代收集器
    • Serial收集器的老年代版本,同樣為單線程
    • 使用標記-整理算法
  • Paraller Old收集器

    Paraller Scavenge_Paraller Old收集器運行示意圖.png

    • 老年代收集器
    • Paraller Scavenge收集器的老年代版本
    • 使用標記-整理算法
  • CMS收集器(Concurrent Mark Sweep)

    CMS收集器運行示意圖.png

    • 老年代收集器
    • 使用標記-清除算法
    • 目標是獲取最短回收停頓時間,目前很大一部分集中運用在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務端上
    • 整個過程分為四個步驟(前四個):
      • 初始標記:僅僅標記一下GC Roots能直接關(guān)聯(lián)到的對象(即GC Roots的一級節(jié)點的對象)
      • 并發(fā)標記:GC Roots Tracing(根據(jù)初始標記的所有對象向下標記對象),與用戶程序并行
      • 重新標記:修正并發(fā)標記期間因為用戶程序繼續(xù)運作而導致變動的那一部分對象的標記記錄
      • 并發(fā)清除:并發(fā)清除無用對象,與用戶程序并行
      • 重置線程:重置CMS的數(shù)據(jù)結(jié)構(gòu)(相當于讓垃圾清理線程回到第一個步驟準備下一次清理)
    • 優(yōu)點:耗時比較長的并發(fā)標記和并發(fā)清除都與用戶線程并行,所以停頓時間會減少很多;
    • 缺點:
      • 對CPU資源非常敏感:雖然在并行清除、標記等過程中不會停止用戶進程,但是CMS同樣也占用了CPU資源,在CPU資源不是很充足的時候,會對用戶進程影響很大;
      • CMS無法處理浮動垃圾:由于并發(fā)清理時用戶進程也在運行,伴隨用戶進程運行還會產(chǎn)生新的垃圾(浮動垃圾),但是在當此的垃圾收集中無法進行收集,只能在下一次收集中進行收集。因為有浮動垃圾的產(chǎn)生,所以CMS在垃圾收集中需要預留一些空間為并發(fā)程序使用。
      • 使用標記-清除算法:產(chǎn)生內(nèi)存碎片
  • G1收集器(Garbage First)

    G1收集器運行示意圖.png

  1. 將整個堆分成多個大小相等的獨立區(qū)域(Region),雖然保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,他們都是一部分Region的集合。
  2. G1跟蹤各個Region里面的垃圾堆積的價值大小,在后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間優(yōu)先回收價值最大的Region。
  3. Region之間的對象引用以及其他收集器中新生代與老生代之間對象引用,虛擬機是使用Remenbered Set來避免全堆掃描的。
  • 收集區(qū)域為整個堆內(nèi)存,但是也保留新生代和老年代的特征
  • 整體看為標記-整理算法、局部(兩個Region)上看是復制算法實現(xiàn)
  • 優(yōu)點:
    • 并行和并發(fā):可充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢
    • 分代收集:不需要和其他收集器配合即可收集整個堆內(nèi)存
    • 空間整合:整體看為標記-整理算法、局部(兩個Region)上看是復制算法實現(xiàn),不會產(chǎn)生內(nèi)存碎片
    • 可預測的停頓:可指定一個長度的M毫秒時間段內(nèi),垃圾回收時間不得超過M毫秒
      *整個過程

五、內(nèi)存分配與回收策略

堆內(nèi)存分布圖.png

對象主要分配在新生代的Eden區(qū)上,如果啟動了本地線程分配緩存(TLAB),將優(yōu)先在TLAB上分配;
Minor GC:指發(fā)生在新生代的垃圾收集動作
Major GC/Full GC:指發(fā)生在老年代的垃圾收集動作

1.對象優(yōu)先在Eden分配
  • 大多數(shù)情況下,對象在新生代Eden區(qū)中分配。當Eden沒有足夠的空間進行分配時,虛擬機將發(fā)出一次Minor GC
  • JVM提供-XX:+PrintGCDetails參數(shù)打印GC日志
2. 大對象直接進入老年代

大對象:所謂大對象是指,需要大量連續(xù)內(nèi)存空間的Java對象,最典型的大對象就是那種很長的字符串及數(shù)組。在寫程序時應當避免創(chuàng)建出這種大對象,因為它可能會導致JVM提前進行GC

  • JVM提供了-XX:PretenureSizeThreshold參數(shù),使大于這個設(shè)置值的對象直接在老年代分配內(nèi)存。(只對Serial和ParNew兩個收集器有效)
3. 長期存活的對象將進入老年代

JVM給每個對象定義了一個對象年齡計數(shù)器。如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動到Servivor中,并將對象年齡設(shè)置為1。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲),將會被晉升到老年代中。

  • 對象晉升老年代的年齡閾(yu【四聲】)值,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置。
3. 動態(tài)對象年齡的判定

為了能更好的適應不同程度的內(nèi)存狀況,虛擬機并不是永遠地要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

4.空間分配擔保

在發(fā)生Minor GC之前,JVM會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那么Minor GC可以確認是安全的。
如果不成立,則看HandlerPromotionFailure設(shè)置值是否允許擔保失敗。如果允許,那么會檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于將嘗試進行一次Minor GC,如果小于,或者HandlerPromotionFailure設(shè)置為不允許冒險,那這時也要改進為一次Full GC。
不過在JDK1.6之后,HandlerPromotionFailure參數(shù)已經(jīng)不會影響到JVM的空間分配擔保策略。規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,否則進行Full GC。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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