友快網

導航選單

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

今日分享開始啦,請大家多多指教~

第一階段:序列垃圾回收器:jdk1。3。1之前Java虛擬機器僅僅只支援Serial收集器。

第二階段:並行垃圾回收器:隨著多核的出現,Java引入了並行垃圾回收器,充分利用多核效能提升垃圾回收效率。

第三階段:併發標記清理回收器CMS:垃圾回收器可以和應用程式同時執行,降低暫停使用者執行緒執行的時間。

第四階段:G1(併發)回收器:初衷是在清理非常大的堆空間的時候能滿足特定的暫停應用程式的時間,與CMS相比會有更少的記憶體碎片。

1 垃圾回收演算法

1-1 標記清除演算法

演算法概述

優點:回收速度快

缺點:造成記憶體碎片,無法分配大的連續空間。

演算法思想

在Java9之前,Java預設使用的垃圾回收器是ParallelGC,從Java9開始G1作為了預設的垃圾回收器

step1: 第一次掃描,透過GC root物件判斷堆記憶體中哪些物件可以進行垃圾回收,進行標記。

step2: 第二次掃描, 將那些標記的GC root物件進行垃圾回收,只需要將起始記憶體地址與終止記憶體地址放入空閒記憶體區就行。

1-2 標記整理演算法

第一個依舊是標記,第二步會進行一個空間整理,從而不產生碎片。

優點:避免了記憶體碎片

缺點:對空間的整理使得效率比較低下。

1-3 複製演算法

特點:

將管理的記憶體分為2塊區域,from區域與to區域,將那些不需要回收的物件從from區域複製到to區域。複製的過程中完成記憶體區域的整理。之後交換from和to的指向。

優點:不會產生記憶體碎片

缺點:需要雙倍的記憶體空間,記憶體利用率不高,而且複製也需要時間。

1-4 三種垃圾回收演算法總結

注意:實際的JVM垃圾回收演算法中上面的三種演算法是綜合使用的。

2 JVM分代回收演算法

2-1 概述

Garden of Eden:伊甸園 garbage:垃圾

新生代主要由三部分內容組成,分別是Eden區,倖存區from,倖存區to。 通常情況下只有Eden區與倖存區from會存放數目,倖存區to只有垃圾回收時,複製物件會用到。堆記憶體的新生代進行一次垃圾回收(Minor GC),大部分物件都會都會被回收。

老年代通常存放一些經常被使用的物件,一個物件如果經歷多次垃圾回收仍然倖存,那麼該物件會從新生代放入老年代。只有新生代記憶體不足並且老年代記憶體也不足的時候才會觸發full GC對老年代的物件進行垃圾回收。

為什麼需要進行劃分?

實際環境中,物件的生命週期是不同的,老年代的物件生命週期比較長,可能很長時間才進行一次垃圾回收。新生代的物件生命週期比較短,垃圾回收比較頻繁。這種分割槽法方便採用不同的垃圾回收演算法更加有效地進行垃圾回收。

2-2 分代垃圾回收示例

step1:程式剛剛開始執行,產生的物件先放入Eden區,當Eden區放不下的時候。

step2:對Eden區進行Minor GC,並將沒有被垃圾回收的物件複製的倖存區To,然後交換倖存區To和倖存區From,第一次垃圾回收的最終的效果如下圖所示:

step3: 第一次Minor GC, Eden區又有空間可以分配給新的物件使用,經過一段時間Eden又不夠用了,觸發第二次Minor GC, 這次垃圾會檢查Eden區以及倖存區From哪些物件可以存活,並將這些物件複製到倖存區To,然後交換倖存區To和倖存區From,這個時候Eden區又空了出來,可以放置新的物件。

實際垃圾回收過程中,JVM會對每個物件經過垃圾回收倖存下來的次數進行記錄,比如上圖中,倖存區的2個物件經過垃圾回收的次數分別是1和2。

step4: 當一些物件經過垃圾回收的次數仍然倖存的次數達到一個閾值(說明這個物件價值比較高),那麼這個物件會被移動到老年代。

極端情況考慮:Eden區,from區,老年區都已經滿了?

此時會觸發Full GC(優先Minor GC,Minor GC依舊記憶體不夠)

2-3 分代垃圾回收的總結

物件首先分配在伊甸園區域

新生代空間不足時,觸發 minor gc,伊甸園和 from 存活的物件使用 copy複製到 to 中,存活的物件年齡加1並且交換from to。

minor gc 會引發stop the world,暫停其它使用者的執行緒,等垃圾回收結束,使用者執行緒才恢復執行暫停時間較短,由於新生代大部分物件都是垃圾,複製的物件很少,所以效率較高。

當物件壽命超過閾值時,會晉升至老年代,最大壽命是15(4bit,物件頭儲存)。

當老年代空間不足,會先嚐試觸發 minor gc,如果之後空間仍不足,那麼觸發full gc,STW的時間更長。

Full GC 的stop the world的時間要比MInor GC時間長,老年代存活物件較多加上空間整理時間,所以停止時間會較長。如果Full GC後,空間仍然不足會觸發記憶體不足的異常。

2-4 垃圾回收相關的虛擬機器引數

垃圾回收器概述

2-5 垃圾回收案例分析

情況1:什麼都不放的情況

new generation:新生代 tenured generation:老年代

情況1執行結果

可以看到即使使用者沒有建立物件,系統物件也要佔據一部分堆記憶體空間。

Java的記憶體物件都是分配在堆上嗎

情況2:新生代堆空間放滿,觸發GC

情況2執行結果

情況3: 新生代記憶體隨著物件的增多放不下了

執行結果

新生代放不下,將新生代的物件放置到老年代。

情況4:一開始直接分配大於新生代的記憶體,如果老年代放得下,則直接放到老年代

執行結果

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

當記憶體比較緊張的時候,即新生代記憶體放不下的時候,有時候會直接將物件分配到老年代,或者直接在回收次數較少(未達到15次)的情況下,直接將新生代物件弄到老年代。

2 垃圾回收器

2-1 垃圾回收器概述

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

CMS垃圾回收器後來被G1垃圾回收器取代。

2-2 序列垃圾回收器

開啟序列垃圾回收器的JVM引數

-XX:+UseSerialGC    = Serial + SerialOld

// Serial:工作在新生代,採用複製的垃圾回收演算法

// SerialOld:工作在老生代,採用標記+整理的垃圾回收演算法

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

總結:觸發垃圾回收時,讓多個執行緒在一個安全點停下來,然後使用單執行緒的垃圾回收器去進行垃圾回收,垃圾回收完成後,再讓其他執行緒執行。

2-3 吞吐量優先的垃圾回收器

開啟吞吐量優先的垃圾回收器的JVM引數

開啟/關閉的引數

預設的多執行緒垃圾回收器,前者是開啟新生代回收器,採用複製演算法,後者是開啟老年代回收器,採用標記+複製演算法。下面選項只要開啟一個,那麼另外一個也會開啟。

-XX:+UseParallelGC , -XX:+UseParallelOldGC

開啟自適應動態調整新生代的大小,晉升閾值

-XX:+UseAdaptiveSizePolicy

二個指標調整的引數(ParallelGC會根據設定的指標去調整堆的大小到達下面期望設定的目標)

指標1)1/(1+ratio) = 垃圾回收的時間/總的執行時間

ratio預設值

99,即垃圾回收的時間不超過總時間1%。但一般設為19。

如果達不到目標,ParallelGC會調整堆記憶體大小來達到這個目標,通常是調大,這樣垃圾回收的次數會減少,從而提高吞吐量

-XX:GCTimeRatio=ratio

指標2)每次垃圾回收的時間限制( 最大暫停的毫秒數)

預設值是200ms

顯然將堆記憶體空間變小有助於減少每次垃圾回收的時間

-XX:MaxGCPauseMillis=ms

總結:顯然指標1)與指標2)是有衝突的。

-XX:ParallelGCThreads=n //垃圾回收並行的執行緒數目

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

總結:採用多執行緒方式進行垃圾回收,垃圾回收的執行緒數目通常根據CPU的核數進行設定。在垃圾回收階段,並行的垃圾回收執行緒會充分佔用CPU。在非垃圾回收階段,使用者執行緒會充分利用CPU資源。

2-4 響應時間優先的垃圾回收器(CMS垃圾回收器)

缺點:採用的標記清除演算法產生記憶體碎片需要退化成單執行緒的垃圾整理回收器,造成響應時間變長。

開啟的JVM引數

注意這個是併發的採用標記清除演算法的垃圾回收,這裡區別於之前的垃圾回收器,該垃圾回收器能夠在進行垃圾回收的同時執行其他非垃圾回收執行緒(也存在時間階段需要停止,但不是所有階段停止)。

老年代併發的垃圾回收器會出現失敗的情況,這時老年代垃圾回收器會退化成單執行緒的垃圾回收器(SerialOld)

-XX:+UseConcMarkSweepGC // use concurrent mark sweep(會產生垃圾碎片) 工作在老年代的垃圾回收器

-XX:+UseParNewGC        // 工作在新生代的垃圾回收器

重要的初始引數

-XX:ParallelGCThreads=n        // 並行的垃圾回收執行緒數,通常等於CPU的核心數(垃圾回收並行階段)

-XX:ConcGCThreads=threads      // 併發的執行緒數目,通常設為並行垃圾回收執行緒數的1/4(垃圾回收併發階段)

其他引數

-XX:CMSInitiatingOccupancyFraction=percent // 執行垃圾回收的記憶體佔比,預留空間給浮動垃圾

-XX:+CMSScavengeBeforeRemark

// 在重新標記前,對新生代進行垃圾回收,減少併發清理的垃圾物件,+開啟,-關閉

華為二面:你真的明白Java垃圾回收器嗎?我用這份筆記狂懟面試官

浮動垃圾是指併發清理過程中使用者執行緒新產生的垃圾,需要等待下次併發清理。

併發工作流程概述:

step1:老年代發生記憶體不存的現象。

step2:ConcMarkSweepGC會進行一個初始標記動作(初始標記需要STW即阻塞非垃圾回收執行緒),初始標記只標記根物件,所以速度非常快,暫停時間也非常短。

step3:完成初始標記後,之前阻塞的執行緒又可以運行了,這個時候垃圾回收執行緒進行併發標記。

step4:併發標記結束後,需要再次阻塞非垃圾回收執行緒,進行一個所謂的重新標記,

step5:重新標記完成後,阻塞的執行緒又可以運行了。垃圾回收執行緒也併發的清理垃圾物件。

總結:初始標記與重新標記需要阻塞執行緒。 在併發階段,由於垃圾回收執行緒佔用資源,所以系統的吞吐量會受到一定的影響,但是系統的響應速度由於併發執行不會受到垃圾回收的明顯影響(相比較其他垃圾回收器,STW時間只需要進行初始標記與重新標記,並且能夠不阻塞其他執行緒進行垃圾的標記與清除)。

今日份分享已結束,請大家多多包涵和指點!

上一篇:從iPhone換為Android手機之後,你有怎樣的不同體驗?
下一篇:【週末特惠】中國最大的小龍蝦出口基地,在潛江,被人稱為中國蝦都