天天看點

Java基礎---JVM垃圾回收機制

一、GC算法(垃圾回收器是算法的落地實作)

1.引用計數

2.标記清楚

3.複制算法

4.标記整理

引用計數:

因為其存在互相引用的嚴重缺陷已棄用,此處就不詳談了

标記清除:

這是最基礎的垃圾回收算法,之是以說它是最基礎的是因為它最容易實作,思想也是最簡單的。标記-清除算法分為兩個階段:标記階段和清除階段。标記階段的任務是标記出所有需要被回收的對象,清除階段就是回收被标記的對象所占用的空間。(标記的過程其實就是檢查哪些對象能被外界通路的可達性分析算法的過程,周遊所有的GC Roots對象,對從GC Roots對象可達的對象都打上一個辨別,一般是在對象的header中,将其記錄為可達對象。)

缺點:

1、效率問題。标記和清除兩個階段的效率都不高,因為這兩個階段都需要周遊記憶體中的對象,很多時候記憶體中的對象執行個體數量是非常龐大的,這無疑很耗費時間,而且GC時需要停止應用程式,這會導緻非常差的使用者體驗。

2、空間問題。标記清除之後會産生大量不連續的記憶體碎片,記憶體空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾回收動作。

複制算法:

此算法的提出是為了解決上面标記算法存在記憶體碎片的缺陷。它将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體碎片的問題。這就是我們新生代中所采用的的算法,也即說明了為什麼有兩個Survive區,且其中的from 、TO兩個區是可以交替的說到這裡再加深下記憶young區的比例為8:1:1

 

缺點:

1、将記憶體縮小為原來的一半,浪費了一半的記憶體空間,代價太高;

2、如果對象的存活率很高,極端一點的情況假設對象存活率為100%,那麼我們需要将所有存活的對象複制一遍,耗費的時間代價也是不可忽視的。

标記整理:

是在标記清除法基礎上做了優化,把存活的對象壓縮到記憶體一端,然後直接清理掉端邊界以外的記憶體(老年代使用的就是标記壓縮法)

分代收集算法:

個人認為分代收集算法就是上面三種算法的一種合理應用,沒有最好的算法隻有更合适的算法。根據每個分區不同的特性采取了最适合其的垃圾回收算法(我還是個小白可能了解有誤。。。。不過這個文章不會讓别人看到吧。。。。。。。感覺自己在寫日記)

說道這裡稍微說一下1.8之後heap的變化

元空間:

其實從JDK1.7開始就已經在逐漸取代之前的永久代,這個東西好像是因為oracle收購了兩家做JVM的公司合并後的結果。。。。。個人觀點可能并不準确。說到這裡提一下常量池的變化因為他們之間有一定的關聯。1.6版本時常量池在方法區、1.7常量池在堆、1.8常量池轉移到元空間如果文中有什麼錯誤希望大家糾正。

元空間特點:

在邏輯分區上元空間在heap中,其實從實體記憶體角度分析元空間和堆并沒有太大關聯。因為元空間在實體記憶體上是與堆分離的。堆占用的是虛拟機記憶體,而元空間占用的是本地實體記憶體。

二、GC回收器

先放兩張關系圖

Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制

串行收集器(Serial):

Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有使用者線程。Serial收集器是針對新生代的收集器,采用的是Copying算法,Serial Old收集器是針對老年代的收集器,采用的是Mark-Compact算法。它的優點是實作簡單高效,但是缺點是會給使用者帶來停頓。不适合伺服器場景。

并行收集器(Parallel)

多個垃圾收集線程并行進行工作,此時使用者是暫停的。适用于科學計算,大資料處理。

CMS并發收集器(concurrent mark sweep)

一種以擷取最短回收停頓時間為目标的收集器,它是一種并發收集器,采用的是Mark-Sweep算法。網際網路公司多用此收集器,适用于對響應時間有要求的場景。

JAVA8之前主要是上訴三種垃圾回收器

G1收集器(1.7開始準備1.8正式上線)

将堆分割為不同的區域(就像上面圖一樣的你懂的一塊一塊的)然後并發對其進行垃圾回收

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx華麗的分割線xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

從java9開始預設垃圾回收器改為G1

java11推出更高版本的ZGC(這個東東我還不了解)

好多舊的文章中提到G1是最前沿的垃圾回收器,當今早已經不是了。我個小白好像沒資格談這些。。。。

垃圾收集器詳解

接下來我們深入了解下各種垃圾回收器的實際應用

首先我們用指令來看一下預設的垃圾回收器是什麼 :java -XX:+PrintCommandLineFlags -version

Java基礎---JVM垃圾回收機制

如圖我們可以清楚的看到預設使用的是ParallelGC,本菜鳥環境是1.8的特此說明一下

我們可以通過 -XX:+UseSerialGC 來更改JVM的垃圾回收器(檢視是否修改成功:jinfo -flags UseSerialGC PID )這裡就不截圖示範了可以自己試試哈。

1.Serial/Serial Old

2.ParNew

3.Parallel Scavenge

4.Parallel Old

5.CMS

6.G1

上面就是常用的7大垃圾回收器,但是Serial Old已基本廢棄,下面我們具體說說其搭配使用,文章開頭也有其組合關系圖, 飛雷神之術,可以傳回去看一下

上圖讓我們看一下不同分區所對應的垃圾回收器:

Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制

寫到這裡需要提醒大家:JVM server / client 兩種模式需要注意一下。不過目前我們基本不會接觸到client模式 在32位環境下JVM會是client模式,client模式下的GC是有所不同的具體我也不太懂哈哈哈,就不往下說了。

Serial收集器:

對應JVM參數:XX:+UseSerialGC 預設開啟與之對應的 SerialOldGC(負責老年代)

Serial收集器是一個新生代收集器,單線程執行,使用複制算法。它在進行垃圾收集時,必須暫停其他所有的工作線程(使用者線程)。是Jvm client模式下預設的新生代收集器。對于限定單個CPU的環境來說,Serial收集器由于沒有線程互動的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。

Serial Old收集器

1、Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“标記-整理”算法。

2、主要意義也是在于給Client模式下的虛拟機使用。

3、如果在Server模式下,那麼它主要還有兩大用途:

一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用[1],

另一種用途就是作為CMS收集器的後備預案,在并發收集發生Concurrent Mode Failure時使用。

ParNew收集器

1、Serial收集器的多線程版本

2、單CPU不如Serial,因為存線上程互動的開銷

-XX:+UseParNewGC 新生代并行(ParNew),老年代串行(Serial Old)這種情況已經不推薦使用

-XX:ParallelGCThreads=n 設定并行收集器收集時使用的CPU數。并行收集線程數。一般最好和計算機的CPU相當

PrarllelScanvenge收集器

-XX:+UseParallelGC 新生代使用并行回收收集器,老年代使用串行收集器

1、吞吐量優先”收集器

2、新生代收集器,複制算法,并行的多線程收集器

3、目标是達到一個可控制的吞吐量(Throughput)。

4、吞吐量=運作使用者代碼時間/(運作使用者代碼時間+垃圾收集時間),虛拟機總共運作了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

5、兩個參數用于精确控制吞吐量:

-XX:MaxGCPauseMillis 是控制最大垃圾收集停頓時間

-XX:GCTimeRatio 直接設定吞吐量大小

-XX:+UseAdaptiveSizePolicy 動态設定新生代大小、Eden與Survivor區的比例、晉升老年代對象年齡

6、并行(Parallel):指多條垃圾收集線程并行工作,但此時使用者線程仍然處于等待狀态。

7、并發(Concurrent):指使用者線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行),使用者程式在繼續運作,而垃圾收集程式運作于另一個CPU上。

Parallel Old收集器

-XX:+UseParallelOldGC 新生代和老年代都使用并行回收收集器

1、Parallel Scavenge收集器的老年代版本,使用多線程和“标記-整理”算法。

2、在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。

CMS收集器

1、以擷取最短回收停頓時間為目标的收集器。

2、非常符合網際網路站或者B/S系統的服務端上,重視服務的響應速度,希望系統停頓時間最短的應用

3、基于“标記—清除”算法實作的

4、CMS收集器的記憶體回收過程是與使用者線程一起并發執行的

5、它的運作過程分為4個步驟,包括:

      1.初始标記,“Stop The World”,隻是标記一下GC Roots能直接關 聯到的對象,速度很快

      2.并發标記,并發标記階段就是進行GC RootsTracing的過程

      3.重新标記,Stop The World”,是為了修正并發标記期間因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,但遠比并發标記的時間短

      4.并發清除(CMS concurrent sweep)

Java基礎---JVM垃圾回收機制

6、優點:并發收集、低停頓

7、缺點:同時進行增加堆記憶體的占用也就是說CMS必須在老年代用盡前完成GC,否則回收失敗,觸發保底的serialOld垃圾收集器,進而造成長時間停頓會産生大量碎片

對CPU資源非常敏感。

無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導緻另一次Full GC的産生。

一款基于“标記—清除”算法實作的收集器

-XX:+UseConcMarkSweepGC 應用CMS收集器

-XX:ConcGCThreads 設定并發線程數量

-XX:CMSInitiatingOccupancyFraction 設定當老年代空間實用率達到百分比值時進行一次cms回收,預設為68,當老年代的空間使用率達到68%的時候,會執行CMS回收

如果記憶體使用率增長的很快,在CMS執行的過程中,已經出現了記憶體不足的情況,此時CMS回收就會失敗,虛拟機将啟動老年代串行回收器進行垃圾回收,這回導緻應用程式中斷,直到垃圾回收完成後才會正常工作,這個過程GC的停頓時間可能較長,是以該值需要根據實際情況設定。

-XX:+UseCMSCompactAtFullCollection 設定cms在垃圾收集完成後進行一次記憶體碎片整理

-XX:CMSFullGCsBeforeCompaction 設定進行多少次cms回收後,進行一次記憶體壓縮。

Java基礎---JVM垃圾回收機制

G1(Garbage-First)收集器

1、當今收集器技術發展的最前沿成果之一

2、G1是一款面向服務端應用的垃圾收集器。

3、優點:并行與并發:充分利用多CPU、多核環境下的硬體優勢分代收集:不需要其他收集器配就能獨立管理整個GC堆空間整合:“标記—整理”算法實作的收集器,局部上基于“複制”算法不會産生記憶體空間碎片可預測的停頓:能讓使用者明确指定在一個長度為M毫秒的時間片段内,消耗在垃圾收集上的時間不得超過N毫秒

4、G1收集器的運作大緻可劃分為以下幾個步驟:

      1.初始标記:标記一下GC Roots能直接關聯到的對象,需要停頓線程,但耗時很短

      2.并發标記:是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與使用者程式并發執行

      3.最終标記:修正在并發标記期間因使用者程式繼續運作而導緻标記産生變動的那一部分标記記錄

      4.篩選回收:對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來制定回收計劃

-XX:+UserG1Gc 應用G1收集器

-XX:MaxGCPauseMillis 指定最大停頓時間

-XX:ParallelGCThreads 設定并行回收的線程數量

G1特點:

Java基礎---JVM垃圾回收機制

G1記憶體劃分原理:

Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制
Java基礎---JVM垃圾回收機制

結尾我們來看一道目前很流行的面試題

github上有面試題騰訊曾問道過:

談談CMS與G1的差別。。。。

說實話第一次看到一臉懵逼不過學習過後我們還是可以回答的哈哈哈

個人答案:

    G1理論上不會産生垃圾碎片,可以精确控制停頓時間(期望),使用範圍不一樣(G1同時用在兩個區),回收過程不一樣。