【重要】第三章:垃圾收集器與內(nèi)存分配策略

Java 和 C++ 之間有一堵由內(nèi)存動(dòng)態(tài)分配垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)去辞槐,墻里面的人卻想出來践付。

3.1 概述

垃圾收集(Garbage Collection廷支,GC)的歷史比Java久遠(yuǎn)频鉴。第一門真正使用內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)的語言是1960年誕生于MIT的Lisp。

需要回收的內(nèi)存區(qū)域:Java堆和方法區(qū)恋拍。
程序計(jì)數(shù)器垛孔,虛擬機(jī)棧和本地方法棧是線程私有的,內(nèi)存分配和回收都具備確定性施敢,不需要過多考慮回收問題周荐。

3.2 對象已死嗎

Java堆里面幾乎所有的對象實(shí)例。垃圾回收前需要確認(rèn)對象是否存活僵娃。

3.2.1 引用計(jì)數(shù)算法

給對象添加一個(gè)引用計(jì)數(shù)器概作,每當(dāng)一個(gè)地方引用它時(shí),計(jì)數(shù)器的值+1,當(dāng)引用失效時(shí)默怨,計(jì) 數(shù)器值-1.任何時(shí)刻計(jì)時(shí)器為0的對象就是不可能再被使用的讯榕。
缺點(diǎn): 無法解決對象之間相互循環(huán)引用的問題。

3.2.2 可達(dá)性分析算法

主流的商用程序語言都是通過可達(dá)性分析(Reachability Analysis)來判定對象是否存活的先壕。
思路:通過一系列被稱為“GC Roots”的對象作為起始點(diǎn)瘩扼,從這些節(jié)點(diǎn)開始向下搜索谆甜,搜索走過的路徑稱為引用鏈垃僚,當(dāng)一個(gè)對象到GC Roots 沒有任何引用鏈相連時(shí),則證明此對象是不可用的规辱。

Java中的GC Roots對象:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象谆棺;
  • 方法區(qū)中類靜態(tài)屬性引用的對象;
  • 方法區(qū)中常量引用的對象;
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象改淑。

3.2.3 再談引用

  • JDK1.2 引用的定義:如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址碍岔,就稱這塊內(nèi)存代表著一個(gè)引用。(被引用 VS 沒有被引用)
  • JDK1.2 之后: 引用分為 強(qiáng)引用朵夏,軟引用蔼啦,弱引用和虛引用4種,引用強(qiáng)度依次遞減仰猖。
    • 1.強(qiáng)引用:代碼中普遍存在捏肢,類似 Object obj = new Object(),只要引用存在,就不能被垃圾回收器回收饥侵。
    • 2.軟引用:描述一些還有用但并非必須的對象鸵赫。在即將發(fā)生內(nèi)存溢出時(shí)才進(jìn)行第二次回收,如果仍沒有足夠內(nèi)存躏升,才拋出OOM異常辩棒。(SoftReference
    • 3.弱引用:非必須對象,只能生存到下一次垃圾回收發(fā)生之前膨疏。一旦執(zhí)行垃圾收集一睁,就會(huì)被釋放內(nèi)存。(WeakReference
    • 4.虛引用:幽靈引用或幻影引用成肘,完全不會(huì)對其生存時(shí)間構(gòu)成影響卖局,也無法通過虛引用來取得一個(gè)對象實(shí)例。存在的目的就是為了能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知双霍。(PhantomReference

3.2.4 生存還是死亡

可達(dá)性分析算法中不可達(dá)對象砚偶,不是直接回收,而是要經(jīng)歷兩次標(biāo)記過程:


image.png

備注:任何一個(gè)對象的finalize()方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次洒闸。finalize() 方法運(yùn)行代價(jià)高染坯,不確定性大,無法保證各個(gè)對象的調(diào)用順序丘逸,強(qiáng)烈建議不使用单鹿。可以用try-finally或其他方法實(shí)現(xiàn)深纲。

3.2.5 回收方法區(qū)

方法區(qū)(或者HotSpot虛擬機(jī)中的永久代)的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類仲锄。

  • 廢棄常量:常量的對象沒有被引用。
  • 無用的類:需要同時(shí)滿足以下三個(gè)條件:
    • 該類的所有實(shí)例都被回收湃鹊,Java堆中不存在該類的任何實(shí)例儒喊;
    • 加載該類的ClassLoader 已經(jīng)被回收;
    • 該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用币呵,無法在任何地方通過反射訪問該類的方法怀愧。

在大量使用反射,動(dòng)態(tài)代理,CGLib等ByteCode框架芯义,動(dòng)態(tài)生成JSP 以及 OSGi 這類頻繁自定義ClassLoader的場景都需要虛擬機(jī)具備類卸載的功能哈垢,以保證永久代不會(huì)溢出。

3.3 垃圾收集算法

  • 標(biāo)記-清除算法 Mark-Sweep
  • 復(fù)制算法 Copying
  • 標(biāo)記-整理算法 Mark-Compact
  • 分代收集算法: 新生代(復(fù)制算法) 老年代(標(biāo)記-清除算法扛拨,標(biāo)記-整理算法)

3.3.1 標(biāo)記-清除算法

最基礎(chǔ)的收集算法是:標(biāo)記-清除算法耘分,首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象绑警。
缺點(diǎn)

  • 1.效率不高
  • 2.產(chǎn)生大量不連續(xù)的內(nèi)存碎片


    image.png

3.3.2 復(fù)制算法

將可用內(nèi)存按容量劃分為大小相等的兩塊陶贼,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了待秃,就將還存活著的對象復(fù)制到另一塊內(nèi)存拜秧,然后再把已使用的內(nèi)存空間一次清理掉。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單章郁,運(yùn)行高效枉氮。
缺點(diǎn):內(nèi)存利用率只有一半。


image.png

現(xiàn)代的商業(yè)虛擬機(jī)都是采用復(fù)制算法來回收新生代暖庄,因?yàn)樾律械膶ο?8%都是短暫存在的聊替,
新生代: Eden :Survivor1 : Survivor2 = 8 :1 :1
老年代:為新生代進(jìn)行分配擔(dān)保。新生代:老年代 = 1 :2

3.3.3 標(biāo)記-整理算法

標(biāo)記-整理算法:首先標(biāo)記出所有需要回收的對象培廓,然后讓所有存活的對象都向一端移動(dòng)惹悄,最后直接清理掉邊界以外的內(nèi)存。


image.png

3.4 HotSpot的算法實(shí)現(xiàn)

3.4.1 枚舉根節(jié)點(diǎn)

GC Roots的節(jié)點(diǎn):

  • 全局性的引用(例如常量或類靜態(tài)屬性)
  • 執(zhí)行上下文(例如棧幀中的本地變量表)

一致性:分析過程中對象引用關(guān)系保持不變肩钠,所以必須停頓所有的Java線程(Stop The World)泣港。
即使是在號稱(幾乎)不會(huì)發(fā)生停頓的CMS收集器中,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的价匠。

目前的主流Java虛擬機(jī)使用的都是準(zhǔn)確式GC当纱,所以有辦法直接得知哪些地方存放著對象的引用,而不需要一個(gè)不漏的檢查完所有執(zhí)行上下文和全局的引用位置踩窖。
在HotSpot的實(shí)現(xiàn) 坡氯,是使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來達(dá)到這個(gè)目的的。

3.4.2 安全點(diǎn) Safe Point

在OopMap的協(xié)助下洋腮,HotSpot可以快速且準(zhǔn)確的完成GC Roots 枚舉箫柳。
程序執(zhí)行不是在所有的地方都能停頓下來開始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停下來開始GC啥供。
安全點(diǎn)的標(biāo)準(zhǔn): 是否具有讓程序長時(shí)間執(zhí)行的特征悯恍。

3.4.3 安全區(qū)域 Safe Region

安全區(qū)域是指一段代碼片段之中,引用關(guān)系不會(huì)發(fā)生變化滤灯。在這個(gè)區(qū)域中的任意地方開始GC都是安全的坪稽。
在線程要離開Safe Region時(shí),他要檢查是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者整個(gè)GC過程):

  • 如果完成了鳞骤,線程繼續(xù)執(zhí)行窒百;
  • 如果還沒有完成,線程需要等待直到接收可以安全離開Safe Region的信號為止豫尽。

3.5 垃圾收集器

  • 收集算法是內(nèi)存回收的方法論篙梢。
  • 垃圾收集器是內(nèi)存回收的具體實(shí)現(xiàn)。

下面討論的收集器是JDK1.7之后的HotSpot虛擬機(jī):


HotSpot虛擬機(jī)中的垃圾收集器

上面一共有7種作用于不同分代的垃圾收集器美旧。兩個(gè)收集器之間的連線表明他們可以搭配使用渤滞。
直到目前為止還沒有最好的收集器,更加沒有萬能的收集器榴嗅。只有對具體應(yīng)用最適合的收集器妄呕。

Serial收集器 --> Parallel收集器 --> CMS --> G1
從JDK1.3 --> JDK1.7 用戶線程停頓時(shí)間不斷縮短,但仍然無法完全消除

參考文章

3.5.1 Serial 收集器

  • Serial收集器是最基本嗽测、發(fā)展歷史最悠久的收集器绪励,曾是(JDK1.3.1之前)虛擬機(jī)新生代收集的唯一選擇。
  • Serial收集器是一個(gè)單線程的收集器唠粥∈栉海“單線程”的意義不僅僅是它只會(huì)使用一個(gè)CPU或一條收集器線程去完成垃圾收集工作,更重要的是它在垃圾收集的時(shí)候晤愧,必須暫停其他所有工作的線程大莫,直到它收集結(jié)束。
  • Serial收集器是HotSpot虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器官份。
  • Serial收集器具有簡單而高效只厘,由于沒有線程交互的開銷,可以獲得最高的單線程收集效率(在單個(gè)CPU環(huán)境中)舅巷。
  • "-XX:+UseSerialGC":添加該參數(shù)來顯式的使用Serial垃圾收集器懈凹。


    Serial / Serial Old 收集器運(yùn)行示意圖

3.5.2 ParNew 收集器

  • ParNew 收集器其實(shí)就是 Serial 收集器的多線程版本。除了使用多條線程進(jìn)行垃圾收集之外悄谐,其余行為包括Serial收集器可用的所有控制參數(shù)介评、收集算法、Stop The Word爬舰、對象分配規(guī)則们陆、回收策略等都與Serial收集器一樣。
  • ParNew收集器是許多運(yùn)行在Server模式下的虛擬機(jī)首選的新生代收集器情屹,其中一個(gè)原因是坪仇,除了Serial收集器之外,目前只有ParNew收集器能與CMS收集器配合工作垃你。
    • "-XX:+UseConcMarkSweepGC":指定使用CMS后椅文,會(huì)默認(rèn)使用ParNew作為新生代收集器喂很。
    • "-XX:+UseParNewGC":強(qiáng)制指定使用ParNew。
    • "-XX:ParallelGCThreads":指定垃圾收集的線程數(shù)量皆刺,ParNew默認(rèn)開啟的收集線程與CPU的數(shù)量相同少辣。


      ParNew / Serial Old 收集器運(yùn)行示意圖

并行(Parallel):指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)羡蛾。
并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行漓帅,可能是交替執(zhí)行),用戶線程繼續(xù)工作痴怨,而垃圾收集程序運(yùn)行在另一個(gè)CPU上忙干。

3.5.3 Parallel Scavenge 收集器 (吞吐量優(yōu)先收集器)

  • Parallel Scavenge收集器是一個(gè)新生代收集器,使用復(fù)制算法浪藻,且是并行的多線程收集器捐迫。
  • 關(guān)注點(diǎn)不同:
    • Parallel Scavenge收集器關(guān)注點(diǎn)是達(dá)到一個(gè)可控制的吞吐量(吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)) =》 高效率的利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù)爱葵,主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)弓乙。
    • 其他收集器關(guān)注點(diǎn)在盡可能的縮短垃圾收集時(shí)用戶線程的停頓時(shí)間。 =》 停頓時(shí)間越短就越適合需要與用戶交互的程序钧惧,良好的響應(yīng)速度能提升用戶體驗(yàn)暇韧。
  • Parallel Scavenge收集器提供了兩個(gè)參數(shù)來用于精確控制吞吐量,一是控制最大垃圾收集停頓時(shí)間的 -XX:MaxGCPauseMillis參數(shù)浓瞪,二是直接設(shè)置吞吐量大小的 -XX:GCTimeRatio參數(shù)懈玻;
    • “ -XX:MaxGCPauseMillis” 參數(shù)允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡可能的保證內(nèi)存垃圾回收花費(fèi)的時(shí)間不超過設(shè)定的值(但是乾颁,并不是越小越好涂乌,GC停頓時(shí)間縮短是以犧牲吞吐量和新生代空間來換取的,如果設(shè)置的值太小英岭,將會(huì)導(dǎo)致頻繁GC湾盒,這樣雖然GC停頓時(shí)間下來了,但是吞吐量也下來了诅妹。
      例如: 【新生代 500MB罚勾,10秒收集一次,每次停頓100毫秒】
      對比 :【新生代 300MB吭狡,5秒收集一次尖殃,每次停頓70毫秒】。
    • “ -XX:GCTimeRatio”參數(shù)的值是一個(gè)大于0且小于100的整數(shù)划煮,也就是垃圾收集時(shí)間占總時(shí)間的比率送丰,默認(rèn)值是99,就是允許最大1%(即1/(1+99))的垃圾收集時(shí)間弛秋。
  • “-XX:UseAdaptiveSizePolicy” 是一個(gè)開關(guān)參數(shù)器躏,如果這個(gè)參數(shù)打開之后俐载,虛擬機(jī)就不需要手工指定新生代的大小(-Xmm)登失,Eden與Survivor區(qū)的比例(-XX:SurvivorRatio),晉升老年代對象大卸粲丁(-XX:PertenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)運(yùn)行情況收集監(jiān)控信息壁畸,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略茅茂。
    Parallel Scavenge / Parallel Old 收集器運(yùn)行示意圖

3.5.4 Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本捏萍。也同樣是一個(gè)單線程收集器。

  • Serial 收集器: 新生代空闲,復(fù)制算法
  • Serial Old 收集器: 老年代令杈,標(biāo)記-整理算法
    主要是給Client模式下的虛擬機(jī)使用。
    在Server模式下碴倾,主要是有兩個(gè)用途:
    • 在JDK1.5及之前版本與 Parallel Scavenge收集器搭配使用
    • 作為CMS收集器的后備預(yù)案逗噩。


      Serial / Serial Old 收集器運(yùn)行示意圖

3.5.5 Parallel Old 收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法進(jìn)行垃圾回收跌榔。其通常與Parallel Scavenge收集器配合使用异雁,“吞吐量優(yōu)先”是這個(gè)組合的特點(diǎn),在注重吞吐量和CPU資源敏感的場合僧须,都可以使用這個(gè)組合纲刀。

Parallel Scavenge / Parallel Old 收集器運(yùn)行示意圖

3.5.6 CMS 收集器

CMS(Concurrent Mark Sweep) 收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器.
運(yùn)作過程相對復(fù)雜,可以分為4個(gè)步驟:

    1. 初始標(biāo)記(CMS initail mark):需要Stop The World, 只是標(biāo)記 GC Roots 能直接關(guān)聯(lián)的對象,速度很快.
    1. 并發(fā)標(biāo)記(CMS concurrent mark):GC Roots Tracing的過程
    1. 重新標(biāo)記(CMS remark):需要Stop The World, 時(shí)間比初始標(biāo)記稍長,比并發(fā)標(biāo)記短. 修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄.
    1. 并發(fā)清除(CMS concurrent sweep): 并發(fā)清除內(nèi)存.
Concurrent Mark Sweep 收集器運(yùn)行示意圖

優(yōu)點(diǎn): 并發(fā)收集, 低停頓
缺點(diǎn):

  • 1.CMS 收集器對CPU的資源非常敏感, 因?yàn)椴l(fā)會(huì)占用資源.
  • 2.CMS 收集器無法處理浮動(dòng)垃圾,可能出現(xiàn) Concurrent Mode Failure 失敗而導(dǎo)致另一次 Full GC 的產(chǎn)生.
    1. "標(biāo)記-清除" 算法, 會(huì)有大量的空間碎片.

3.5.7 G1收集器

G1(Garbage-First) 收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿成果之一.
G1 是一款面向服務(wù)端應(yīng)用的垃圾收集器.HotSpot 開發(fā)團(tuán)隊(duì)賦予它的使命是未來可以替換掉JDK1.5中發(fā)布的CMS收集器. 與其他收集器相比,G1具備以下特點(diǎn):

  • 并行與并發(fā): G1 能充分利用多CPU, 多核環(huán)境下的硬件優(yōu)勢, 使用多個(gè)CPU來縮短Stop The World 停頓時(shí)間, 部分其他收集器原本需要停頓Java線程的GC動(dòng)作, G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)運(yùn)行.
  • 分代收集: G1收集器保留分代收集的概念.雖然G1可以不需要通過其他收集器配合就能獨(dú)立管理整個(gè)GC堆,但它能夠采用不同的方式區(qū)處理新創(chuàng)建的對象和已經(jīng)存活了一段時(shí)間,熬過多次GC的舊對象以獲取更好的手機(jī)效果.
  • 空間整合: 與CMS的"標(biāo)記-清除"相比,G1 從整體來看是基于"標(biāo)記-整理"算法實(shí)現(xiàn)的收集器. 從局部來看,是基于"復(fù)制"算法來實(shí)現(xiàn)的. 所以G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存碎片.
  • 可預(yù)測的停頓: 降低停頓時(shí)間是G1和CMS的共同關(guān)注點(diǎn). 但G1除了最求低停頓外,還能建立可預(yù)測的停頓時(shí)間模型, 能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒. 這幾乎是實(shí)時(shí)Java(RTSJ) 的垃圾收集器的特征了.

在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代. 而使用G1收集器時(shí), Java堆的內(nèi)存布局就與其他的收集器有很大的差別.
G1將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region), 雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的,他們都是一部分不需要連續(xù)的Region的集合.

G1收集器之所以能建立可預(yù)測的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集. G1 跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收所獲得的空間大小以及回收所需要時(shí)間的經(jīng)驗(yàn)值), 在后臺維護(hù)一個(gè)優(yōu)先列表, 每次根據(jù)允許的收集時(shí)間, 優(yōu)先回收價(jià)值最大的Region(這個(gè)也是 Garbage-First名稱的由來). 這種使用Region 劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式, 保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率.

G1按照Region劃分內(nèi)存的思路是好的,但實(shí)現(xiàn)起來卻是很難的,因?yàn)镽egion不可能是孤立的. 一個(gè)對象分配在某個(gè)Region中, 它并非只能被本Region中的其他對象引用, 而是可以和整個(gè)Java堆中的任意對象發(fā)生引用關(guān)系.

在G1收集器中, Region之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用,虛擬機(jī)都是使用Remembered Set 來避免全堆掃描的. G1中的每個(gè)Region都會(huì)有一個(gè)與之對應(yīng)的Remembered Set.

如果不計(jì)算維護(hù) Remmenbered Set 的操作, G1收集器的運(yùn)作大致可以劃分為以下幾個(gè)步驟:

    1. 初始標(biāo)記(Initial Marking): 需要停頓線程, 耗時(shí)很短. 只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象,并且修改 TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí),能在正確可用的Region中創(chuàng)建新對象.
  • 2.并發(fā)標(biāo)記(Concurrent Marking): 不需要停頓線程, 與用戶程序并發(fā)執(zhí)行. 從GC Roots 開始對堆中對象進(jìn)行可達(dá)性分析,找出存活對象,耗時(shí)較長.
    1. 最終標(biāo)記(Final Marking): 需要停頓線程,但是可以并行執(zhí)行. 修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄, 虛擬機(jī)將這段時(shí)間對象變化記錄在線程 Remembered Set Logs中. 最終標(biāo)記階段需要把 Remembered Set Logs的數(shù)據(jù)合并到 Remembered Set 中.
    1. 篩選回收(Live Data Counting and Evacuation): 首先對各個(gè)Region 的回收價(jià)值和成本進(jìn)行排序, 根據(jù)用戶所期望的GC停頓時(shí)間來指定回收計(jì)劃.
G1 收集器運(yùn)行示意圖

3.5.8 理解GC日志

閱讀GC日志是處理Java虛擬機(jī)內(nèi)存問題的基礎(chǔ)技能, 它只是一些認(rèn)為確定的規(guī)則, 沒有太多的技術(shù)含量.

33.125: [GC (Allocation Failure) --[PSYoungGen: 5591K->5591K(9216K)] 9687K->9751K(15360K), 0.0015296 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

100.667: [Full GC (Ergonomics) [PSYoungGen: 5591K->0K(9216K)] [ParOldGen: 4160K->5133K(6144K)] 9751K->5133K(15360K), [Metaspace: 2632K->2632K(1056768K)], 0.0045365 secs] [Times: user=0.06 sys=0.00, real=0.00 secs]
  • 1、"33.125"和"100.667"這兩個(gè)數(shù)字代表了GC發(fā)生的時(shí)間担平,這個(gè)數(shù)字是從Java虛擬機(jī)啟動(dòng)以來經(jīng)過的秒數(shù)示绊。

  • 2、GC日志開頭的“[GC”和“[FULL GC”說明了這次垃圾收集的停頓類型暂论,而不是用來區(qū)分老年代GC還是新生代GC的面褐。如果有FULL,說明這次GC是發(fā)生了Stop-The-World的取胎。新生代收集器ParNew也會(huì)出現(xiàn)"[Full GC"(這一般是因?yàn)榉峙鋼?dān)保失敗之類的問題展哭,所以才導(dǎo)致STW)。如果是調(diào)用System.gc()方法所觸發(fā)的收集闻蛀,那么在這里將顯示"FULL GC(System)"摄杂。

  • 3、接下來的"[DefNew"循榆、"[Tenured"析恢、"[Perm"表示GC發(fā)生的區(qū)域,這里顯示的區(qū)域名稱與使用的GC收集器都是密切相關(guān)的秧饮。
    例如:使用ParNew收集器中的新生代名為“Default New Generation”映挂,所以顯示的是“[DefNew”泽篮。如果是ParNew收集器,新生代名稱就會(huì)變?yōu)?[ParNew"柑船,意為"Parallel New Generation"帽撑。如果采用Parallel Scavenge收集器,那它配套的新生代稱為"PSYoungGen"鞍时,老年代和永久代同理亏拉,名稱也是由收集器決定的。

  • 4逆巍、后面方括號內(nèi)部的“5591K->0K(9216K)”及塘,含義是“GC前該內(nèi)存區(qū)域已使用容量->GC后該內(nèi)存區(qū)域已使用容量(該內(nèi)存區(qū)域總?cè)萘浚?/strong>”

  • 5、而在方括號之外的“9751K->5133K(15360K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆總?cè)萘浚?/strong>”

  • 6锐极、再往后“0.0015296 secs”表示該內(nèi)存區(qū)域GC所占用的時(shí)間笙僚,單位是秒。有的收集器會(huì)給出更具體的數(shù)據(jù) [Times: user=0.00 sys=0.00, real=0.00 secs]灵再。這里的user肋层、sys和real與Linux的time命令所輸出的時(shí)間含義一致,分別代表用戶態(tài)消耗的CPU時(shí)間翎迁、內(nèi)核態(tài)消耗的CPU時(shí)間和操作從開始到結(jié)束所經(jīng)過的墻鐘時(shí)間(Wall Clock Time)栋猖。墻鐘時(shí)間包括各種各種非運(yùn)算的等待耗時(shí),例如等待磁盤I/O等汪榔,而CPU時(shí)間不包括這些耗時(shí)掂铐。

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

Java 技術(shù)體系中所提倡的自動(dòng)內(nèi)存管理最終可以歸結(jié)為自動(dòng)化的解決兩個(gè)問題:

  • 給對象分配內(nèi)存
  • 回收分配給對象的內(nèi)存

對象的內(nèi)存分配, 往大方向講,就是在堆上分配.對象主要分配在新生代的Eden區(qū)上, 如果啟動(dòng)了本地線程分配緩沖, 將按線程優(yōu)先在TLAB上分配. 少數(shù)情況下也可能直接分配在老年代中. 分配的規(guī)則并不是百分百固定的, 其細(xì)節(jié)取決于當(dāng)前使用的是哪一種垃圾收集器組合,還有虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的設(shè)置.

3.6.1 對象優(yōu)先在Eden分配

大多數(shù)情況下, 對象在新生代Eden區(qū)中分配. 當(dāng)Eden區(qū)中沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次 Minor GC.

  • 新生代GC(Minor GC): 發(fā)生在新生代的垃圾收集動(dòng)作.因?yàn)镴ava對象大多都具備朝生夕滅的特性, 所以Minor GC 會(huì)非常頻繁, 一般回收速度也比較快.
  • 老年代GC(Major GC / Full GC): 發(fā)生在老年代的GC, 出現(xiàn)了Major GC, 經(jīng)常會(huì)伴隨著至少一次的Minor GC(但并非絕對的, 在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程). Major GC的速度一般比Minor GC 慢 10倍以上.

3.6.2 大對象直接進(jìn)入老年代

所謂的大對象是指需要大量連續(xù)內(nèi)存空間的Java對象,最典型的大對象就是那種很長的字符串以及數(shù)組. 大對象對虛擬機(jī)的內(nèi)存分配來說就是一個(gè)壞消息(比遇到一個(gè)大對象更加壞的消息就是遇到一群朝生夕滅的短命大對象).經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時(shí)就提前出發(fā)垃圾收集以獲取足夠的連續(xù)空間來安置它們.

虛擬機(jī)提供了一個(gè) -XX:PertenureSizeThreshold參數(shù),令大于這個(gè)設(shè)置值的對象直接在老年代分配. 以避免在Eden以及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制.

3.6.3 長期存活的對象將進(jìn)入老年代

虛擬機(jī)采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時(shí)就必須能識別哪些對象應(yīng)放在新生代,哪些對象應(yīng)放在老年代中. 為了做到這一點(diǎn), 虛擬機(jī)給每個(gè)對象定義了一個(gè)對象年齡計(jì)數(shù)器.

  • 如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor 容納的話,將被移動(dòng)到Survivor空間中, 并且對象年齡設(shè)為1. 對象在Survivor區(qū)中每熬過一次Minor GC, 年齡就增加1歲. 當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲, 可以通過參數(shù) -XX:MaxTenuringThreshold設(shè)置), 將會(huì)被晉升到老年代中.

3.6.4 動(dòng)態(tài)對象年齡判定

為了更好的適應(yīng)不同程序的內(nèi)存狀況, 虛擬機(jī)并不是永遠(yuǎn)的要求對象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代, 如果在Survivor空間中相同年齡的所有對象大小總和大于Survivor空間的一半, 年齡大于或等于該年齡的對象就直接進(jìn)入老年代.

3.6.5 空間分配擔(dān)保

在發(fā)生Minor GC前, 虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象的總空間. 如果這個(gè)條件成立,那么Minor GC可以確保是安全的.

image.png

JDK1.6之后, HandlePromotionFailure 參數(shù)將會(huì)失效. 只要老年代的連續(xù)空間大于新生代對象總大小或者歷次晉升的平均大小就會(huì)進(jìn)行Minor GC, 否則將進(jìn)行Full GC.

image.png

3.7 本章小結(jié)

本章介紹了垃圾收集的算法, 還有幾款JDK1.7中提供的垃圾收集器特點(diǎn)以及運(yùn)作原理. Java虛擬機(jī)中自動(dòng)內(nèi)存分配及回收的主要規(guī)則.

內(nèi)存回收與垃圾收集器在很多時(shí)候都是影響系統(tǒng)性能,并發(fā)能力的主要因素之一. 虛擬機(jī)之所以提供多種不同的收集器以及提供大量的調(diào)節(jié)參數(shù), 是因?yàn)橹挥懈鶕?jù)實(shí)際應(yīng)用需求,實(shí)現(xiàn)方式選擇最優(yōu)的收集方式才能獲取最高的性能.沒有固定收集器, 參數(shù)組合, 也沒有最優(yōu)的調(diào)優(yōu)方法,虛擬機(jī)也就沒有什么必然的內(nèi)存回收行為.因此,在學(xué)習(xí)虛擬機(jī)內(nèi)存知識,如果要到實(shí)踐調(diào)優(yōu)階段,那么必須了解每個(gè)具體收集器的行為, 優(yōu)勢和劣勢,以及調(diào)節(jié)參數(shù).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揍异,隨后出現(xiàn)的幾起案子全陨,更是在濱河造成了極大的恐慌,老刑警劉巖衷掷,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辱姨,死亡現(xiàn)場離奇詭異,居然都是意外死亡戚嗅,警方通過查閱死者的電腦和手機(jī)雨涛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懦胞,“玉大人替久,你說我怎么就攤上這事□镂荆” “怎么了蚯根?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胀糜。 經(jīng)常有香客問我颅拦,道長蒂誉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任距帅,我火速辦了婚禮右锨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碌秸。我一直安慰自己绍移,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布讥电。 她就那樣靜靜地躺著蹂窖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪允趟。 梳的紋絲不亂的頭發(fā)上恼策,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天鸦致,我揣著相機(jī)與錄音藏畅,去河邊找鬼咖摹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隙弛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼复哆,長吁一口氣:“原來是場噩夢啊……” “哼歧沪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起折砸,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤看疗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后睦授,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體两芳,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年去枷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怖辆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡删顶,死狀恐怖竖螃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逗余,我是刑警寧澤特咆,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站录粱,受9級特大地震影響坚弱,放射性物質(zhì)發(fā)生泄漏蜀备。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一荒叶、第九天 我趴在偏房一處隱蔽的房頂上張望碾阁。 院中可真熱鬧,春花似錦些楣、人聲如沸脂凶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚕钦。三九已至,卻和暖如春鹅很,著一層夾襖步出監(jiān)牢的瞬間嘶居,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工促煮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邮屁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓菠齿,卻偏偏與公主長得像佑吝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子绳匀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容