JVM 是面試的重災區,同樣垃圾回收期也是重中之重。不過我們學習不是為了應付面試,而是處在這個行業中,應該去了解一下。
如果是做JVM調優的,必須要了解一下各種垃圾回收器的特點。進行非常多的參數調優不起作用,有時候簡單的通過修改預設的垃圾回收器,就能起到很好的作用。
來先看看都有哪些垃圾回收器
垃圾回收器,可以進行分類,分為多線程的和單線程的。
垃圾回收器有以下幾個。
1.新生代
- Serial (第一代)
- PraNew (第二代)
- Parallel Scavenge (第三代)
- G1收集器(第四代)
2.老年代
- Serial Old (第一代)
- Parallel Old (第二代)
- CMS (第三代)
- G1收集器 (第四代)

JVM垃圾收集器發展曆程
第一階段,Serial(串行)收集器
在jdk1.3.1之前,java虛拟機僅僅能使用Serial收集器。 Serial收集器是一個單線程的收集器,但它的“單線程”的意義并不僅僅是說明它隻會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。
第二階段,Parallel(并行)收集器
Parallel收集器也稱吞吐量收集器,相比Serial收集器,Parallel最主要的優勢在于使用多線程去完成垃圾清理工作,這樣可以充分利用多核的特性,大幅降低gc時間。
第三階段,CMS(并發)收集器
CMS收集器在Minor GC時會暫停所有的應用線程,并以多線程的方式進行垃圾回收。在Full GC時不再暫停應用線程,而是使用若幹個背景線程定期的對老年代空間進行掃描,及時回收其中不再使用的對象。
第四階段,G1(并發)收集器
G1收集器(或者垃圾優先收集器)的設計初衷是為了盡量縮短處理超大堆(大于4GB)時産生的停頓。相對于CMS的優勢而言是記憶體碎片的産生率大大降低。
垃圾回收期的發展背景
可能記起來有點頭疼,不妨從它的發展曆程這個點出發,來記憶。其實問題就會變得簡單,這是一個為什麼有了自行車,還要發明汽車,汽車明明很快了,為什麼還要有動車的問題 。
JVM的本質就是介于我們寫的程式與作業系統資源的中間層,系統資源又特指 記憶體資源。
而垃圾回收器的發展,也是基于可使用的堆記憶體的擴大。回退十年,由于計算機本身發展的滞後,可用的機器資源,比如記憶體沒有多少,可配置設定給堆記憶體的更是沒有多少,當然當時的業務場景,要用到的資源同樣沒有多少。
我們先對垃圾回收器進行一個分類,分别是多線程的和單線程的。其中serial 就是單線程的,它就适合使用在記憶體非常小的場景下。因為本身堆記憶體沒有多大,即使使用單線程,處理的時間也沒有那麼長。這也是最開始使用serial垃圾回收器的原因。
随着計算機的發展,和業務的增長,高并發的出現,我們需要對堆記憶體進行擴大,而之前的serial 也就不适用了。如果在超大堆記憶體下一定要使用它,隻能表現出來的是系統莫名其妙的卡頓,毋庸置疑,很大一部分時間,就是因為正在垃圾回收,又因為STW的機制(stop the world 垃圾回收的時候,除了垃圾回收以外的工作必須停下來)。是以誕生了 parallel scavenge 垃圾回收器,從名字上就很容易知道,他是多線程的垃圾回收器。多個線程同時工作,從一定程度上縮短 STW的時間。但是這依然不夠,是以誕生了CMS垃圾回收器,由于CMS垃圾回收器,有比較大的bug,它并不是預設的垃圾回收器。JDK 1.8 預設的垃圾回收器還是 Parallel Scavenge。
檢視預設垃圾回收器
windows下,使用CMD 指令行工具,使用 java -XX:+PrintCommandLineFlags -version 這條指令來檢視預設的垃圾回收器。
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266390080 -XX:MaxHeapSize=4262241280 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
UseParallelGC 即 Parallel Scavenge + Parallel Old,再檢視詳細資訊
重點了解一下G1這個垃圾回收器
垃圾回收器進步的目标
垃圾回收器第四代産物。推動垃圾回收器發展的出發點隻有一個,那就是盡可能的減少STW的時間,系統才能更快更多的對外提供服務。
G1 垃圾回收器的應用背景
JVM需要管理超大的堆記憶體
電腦的進步,多核CPU廣泛的應用。
G1垃圾回收器的重要進步思想
G1最大的特點是引入分區的思路,弱化了分代的概念。這是一種分治的思想。同時這也是在特定背景下,應運而生的垃圾回收器。
G1與CMS的比較
- 算法: G1基于标記-整理算法, 不會産生空間碎片,配置設定大對象時不會無法得到連續的空間而提前觸發一次FULL GC。
- 停頓時間可控: G1可以通過設定預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象。
- 并行與并發:G1能更充分的利用CPU,多核環境下的硬體優勢來縮短stop the world的停頓時間。
- CMS中,堆被分為PermGen,YoungGen,OldGen;而YoungGen又分了兩個survivo區域。在G1中,堆被平均分成幾個區域(region),在每個區域中,雖然也保留了新老代的概念,但是收集器是以整個區域為機關收集的。
- G1在回收記憶體後會馬上同時做合并空閑記憶體的工作、而CMS預設是在STW(stop the world)的時候做。
- G1會在Young GC中使用、而CMS隻能在O區使用。
G1的堆記憶體算法
- G1之前的JVM記憶體模型
- 新生代:伊甸園區(eden space) + 2個幸存區
- 老年代
- 持久代(perm space):JDK1.8之前
- 元空間(metaspace):JDK1.8之後取代持久代
2. G1收集器的記憶體模型
1) G1堆記憶體結構
堆記憶體會被切分成為很多個固定大小區域(Region),每個是連續範圍的虛拟記憶體。
堆記憶體中一個區域(Region)的大小可以通過-XX:G1HeapRegionSize參數指定,大小區間最小1M、最大32M,總之是2的幂次方。
預設把堆記憶體按照2048份均分。
2) G1堆記憶體配置設定
每個Region被标記了E、S、O和H,這些區域在邏輯上被映射為Eden,Survivor和老年代。
存活的對象從一個區域轉移(即複制或移動)到另一個區域。區域被設計為并行收集垃圾,可能會暫停所有應用線程。
如上圖所示,區域可以配置設定到Eden,survivor和老年代。此外,還有第四種類型,被稱為巨型區域(Humongous Region)。Humongous區域是為了那些存儲超過50%标準region大小的對象而設計的,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。
G1回收流程
在執行垃圾收集時,G1以類似于CMS收集器的方式運作。
- G1收集器的階段分以下幾個步驟:
1)G1執行的第一階段:初始标記(Initial Marking )
這個階段是STW(Stop the World )的,所有應用線程會被暫停,标記出從GC Root開始直接可達的對象。
2)G1執行的第二階段:并發标記
從GC Roots開始對堆中對象進行可達性分析,找出存活對象,耗時較長。當并發标記完成後,開始最終标記(Final Marking )階段
3)最終标記
标記那些在并發标記階段發生變化的對象,将被回收。
4)篩選回收
首先對各個Regin的回收價值和成本進行排序,根據使用者所期待的GC停頓時間指定回收計劃,回收一部分Region。
最後,G1中提供了兩種模式垃圾回收模式,Young GC和Mixed GC,兩種都是Stop The World(STW)的。
G1的GC模式
- YoungGC年輕代收集
在配置設定一般對象(非巨型對象)時,當所有eden region使用達到最大閥值并且無法申請足夠記憶體時,會觸發一次YoungGC。每次younggc會回收所有Eden以及Survivor區,并且将存活對象複制到Old區以及另一部分的Survivor區。
YoungGC的回收過程如下:
- 根掃描,跟CMS類似,Stop the world,掃描GC Roots對象。
- 處理Dirty card,更新RSet.
- 掃描RSet,掃描RSet中所有old區對掃描到的young區或者survivor去的引用。
- 拷貝掃描出的存活的對象到survivor2/old區
- 處理引用隊列,軟引用,弱引用,虛引用
2. mixed gc
當越來越多的對象晉升到老年代old region時,為了避免堆記憶體被耗盡,虛拟機會觸發一個混合的垃圾收集器,即mixed gc,該算法并不是一個old gc,除了回收整個young region,還會回收一部分的old region,這裡需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進行收集,進而可以對垃圾回收的耗時時間進行控制。
G1沒有fullGC概念,需要fullGC時,調用serialOldGC進行全堆掃描(包括eden、survivor、o、perm)。
何時使用G1
G1的第一個重要特點是為使用者的應用程式的提供一個低GC延時和大記憶體GC的解決方案。這意味着堆大小6GB或更大,穩定和可預測的暫停時間将低于0.5秒。
如果應用程式使用CMS或ParallelOld垃圾回收器具有一個或多個以下特征,将有利于切換到G1:
- Full GC持續時間太長或太頻繁
- 對象配置設定率或年輕代更新老年代很頻繁
- 不期望的很長的垃圾收集時間或壓縮暫停(超過0.5至1秒)
注意:如果你正在使用CMS或ParallelOld收集器,并且你的應用程式沒有遇到長時間的垃圾收集暫停,則保持與您的目前收集器是很好的,更新JDK并不必要更新收集器為G1。