圖文並茂,萬字詳解,帶你掌握 JVM 垃圾回收!

垃圾回收( Garbage Collection 如下簡稱 GC)誕生於1960年 MIT 的 Lisp 語言,有半個多世紀的歷史。java

在Java 中,JVM 會對內存進行自動分配與回收,其中 GC 的主要做用就是清楚再也不使用的對象,自動釋放內存。 面試

GC 相關的研究者們主要是思考這3件事情。算法

  • 哪些內存須要回收?
  • 何時回收?
  • 如何回收?

本文也大體按照這個思路,爲你們描述垃圾回收的相關知識。由於會有不少內存區域相關的知識,但願讀者先學習完《精美圖文帶你掌握 JVM 內存佈局》再來閱讀本文。後端

本文的主要內容以下(建議你們在閱讀和學習的時候,也大體按照如下的思路來思考和學習):緩存

  • 哪些內存須要回收?即GC 發生的內存區域?
  • 如何判斷這個對象須要回收?即GC 的存活標準?

這裏又可以引出如下的知識概念:微信

  • 引用計數法
  • 可達性分析法
  • 引用的種類和特色、區別 (強引用、軟引用、弱引用、虛引用)
  • 延伸知識:(WeakHashMap) (引用隊列)

有了對象的存活標準以後,咱們就須要知道GC 的相關算法(思想)多線程

  • 標記-清除(Mark-Sweep)算法
  • 複製(Copying)算法
  • 標記-整理(Mark-Compact)算法

在下一步學習以前,還須要知道一些GC的術語,防止對一些概念描述出現混淆,知道了算法以後,天然而然咱們到了JVM中對這些算法的實現和應用,即各類垃圾收集器(Garbage Collector)架構

  • 串行收集器
  • 並行收集器
  • CMS 收集器
  • G1 收集器

1、GC 的 目標區域

一句話:GC 主要關注 堆和方法區併發

在精美圖文帶你掌握 JVM 內存佈局一文中,理解介紹了Java 運行時內存的分佈區域和特色。工具

其中咱們知道了程序計數器、虛擬機棧、本地方法棧3個區域是隨線程而生,隨線程而滅的。棧中的棧幀隨着方法的進入和退出而有條不紊地執行着出棧和入棧操做。

每個棧幀中分配多少內存基本上是在類結構肯定下來時就已知的(儘管在運行期會由JIT編譯器進行一些優化,但在本章基於概念模型的討論中,大致上能夠認爲是編譯期可知的),所以這幾個區域的內存分配和回收都具有肯定性,在這幾個區域內就不須要過多考慮回收的問題,由於方法結束或者線程結束時,內存天然就跟隨着回收了。

而堆和方法區則不同,一個接口中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,咱們只有在程序處於運行期間時才能知道會建立哪些對象,這部份內存的分配和回收都是動態的。GC 關注的也就是這部分的內存區域。

2、GC 的存活標準

知道哪些區域的內存須要被回收以後,咱們天然而然地想到了,如何去判斷一個對象須要被回收呢?

對於如何判斷對象是否能夠回收,有兩種比較經典的判斷策略。

  • 引用計數算法
  • 可達性分析算法

1. 引用計數法

在對象頭維護着一個 counter 計數器,對象被引用一次則計數器 +1;若引用失效則計數器 -1。當計數器爲 0 時,就認爲該對象無效了。

主流的Java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的緣由是它很難解決對象之間相互循環引用的問題。發生循環引用的對象的引用計數永遠不會爲0,結果這些對象就永遠不會被釋放。

2. 可達性分析算法 

從GC Roots 爲起點開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots 沒有任何引用鏈相連時,則證實此對象是不可用的。不可達對象。

Java 中,GC Roots 是指:

  • Java 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧中引用的對象
  • 方法區中常量引用的對象
  • 方法區中類靜態屬性引用的對象

3. Java 中的引用 

Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。

這樣子設計的緣由主要是爲了描述這樣一類對象:當內存空間還足夠時,則能保留在內存之中;若是內存空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這樣的應用場景。

也就是說,對不一樣的引用類型,JVM 在進行GC 時會有着不一樣的執行策略。因此咱們也須要去了解一下。

A. 強引用(Strong Reference)

`MyClass obj = new MyClass(); // 強引用``obj = null // 此時‘obj’引用被設爲null了,前面建立的'MyClass'對象就能夠被回收了`

只要強引用存在,垃圾收集器永遠不會回收被引用的對象,只有當引用被設爲null的時候,對象纔會被回收。可是,若是咱們錯誤地保持了強引用,好比:賦值給了 static 變量,那麼對象在很長一段時間內不會被回收,會產生內存泄漏。

B. 軟引用(Soft Reference)

軟引用是一種相對強引用弱化一些的引用,可讓對象豁免一些垃圾收集,只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象。JVM 會確保在拋出 OutOfMemoryError 以前,清理軟引用指向的對象。

軟引用一般用來實現內存敏感的緩存,若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。

SoftReference<MyClass> softReference = new SoftReference<>(new MyClass());

C. 弱引用(Weak Reference)

弱引用的強度比軟引用更弱一些。當 JVM 進行垃圾回收時,不管內存是否充足,都會回收只被弱引用關聯的對象。

WeakReference<MyClass> weakReference = new WeakReference<>(new MyClass());

弱引用能夠引伸出來一個知識點, WeakHashMap&ReferenceQueue

ReferenceQueue 是GC回調的知識點。這裏由於篇幅緣由就不細講了,推薦引伸閱讀:ReferenceQueue的使用

D. 幻象引用/虛引用(Phantom References)

虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。

PhantomReference<MyClass> phantomReference = new PhantomReference<>(new MyClass(), new ReferenceQueue<>());

3、GC 算法

有了判斷對象是否存活的標準以後,咱們再來了解一下GC的相關算法。

  • 標記-清除(Mark-Sweep)算法
  • 複製(Copying)算法
  • 標記-整理(Mark-Compact)算法

1. 標記-清除(Mark-Sweep)算法

標記-清除算法在概念上是最簡單最基礎的垃圾處理算法。

該方法簡單快速,可是缺點也很明顯,一個是效率問題,標記和清除兩個過程的效率都不高;另外一個是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。

後續的收集算法都是基於這種思路並對其不足進行改進而獲得的。

2. 複製(Copying)算法

複製算法改進了標記-清除算法的效率問題。

它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。

這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。

缺點也是明顯的,可用內存縮小到了原先的一半。

如今的商業虛擬機都採用這種收集算法來回收新生代,IBM公司的專門研究代表,新生代中的對象98%是「朝生夕死」的,因此並不須要按照1:1的比例來劃份內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。

在前面的文章中咱們提到過,HotSpot默認的Eden:survivor1:survivor2=8:1:1,以下圖所示。

延伸知識點:內存分配擔保

固然,98%的對象可回收只是通常場景下的數據,咱們沒有辦法保證每次回收都只有很少於10%的對象存活,當Survivor空間不夠用時,須要依賴其餘內存(這裏指老年代)進行分配擔保(Handle Promotion)。

內存的分配擔保就比如咱們去銀行借款,若是咱們信譽很好,在98%的狀況下都能按時償還,因而銀行可能會默認咱們下一次也能按時按量地償還貸款,只須要有一個擔保人能保證若是我不能還款時,能夠從他的帳戶扣錢,那銀行就認爲沒有風險了。內存的分配擔保也同樣,若是另一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象時,這些對象將直接經過分配擔保機制進入老年代。

3. 標記-整理算法

前面說了複製算法主要用於回收新生代的對象,可是這個算法並不適用於老年代。由於老年代的對象存活率都較高(畢竟大多數都是經歷了一次次GC千辛萬苦熬過來的,身子骨很硬朗😎)

根據老年代的特色,提出了另一種標記-整理(Mark-Compact)算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。

4. 分代收集算法

有沒有注意到了,咱們前面的表述當中就引入了新生代、老年代的概念。準確來講,是先有了分代收集算法的這種思想,纔會將Java堆分爲新生代和老年代。這兩個概念之間存在着一個前後因果關係。

這個算法很簡單,就是根據對象存活週期的不一樣,將內存分塊。在Java 堆中,內存區域被分爲了新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。

就如咱們在介紹上面的算法時描述的,在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用 「標記—清理」 或者 「標記—整理」 算法 來進行回收。

  • 新生代:複製算法
  • 老年代:標記-清除算法、標記-整理算法

5. 從新回顧 建立對象時觸發GC的流程

這裏從新回顧一下精美圖文帶你掌握 JVM 內存佈局裏面JVM建立一個新對象的內存分配流程圖。這張圖也描述了GC的流程。

推薦閱讀:46張PPT弄懂JVM、GC算法和性能調優!

關注微信公衆號:Java技術棧,在後臺回覆:JVM,能夠獲取我整理的 N 篇最新JVM 教程,都是乾貨。

4、GC 術語 

在學習垃圾收集器知識點以前,須要向讀者們科普一些GC的術語,方便大家後面理解。

  • 部分收集(Partial GC):指目標不是完整收集整個Java堆的垃圾收集,其中又分爲:
  • 新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集。
  • 老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行爲。另外請注意「Major GC」這個說法如今有點混淆,在不一樣資料上常有不一樣所指,讀者需按上下文區分究竟是指老年代的收集仍是整堆收集。
  • 混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行爲。
  • 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。
  • 並行(Parallel) :在JVM運行時,同時存在應用程序線程和垃圾收集器線程。並行階段是由多個GC 線程執行,即GC 工做在它們之間分配。

    串行(Serial):串行階段僅在單個GC 線程上執行。

    STW :Stop The World 階段,應用程序線程被暫停,以便GC線程 執行其工做。當應用程序由於GC 暫停時,這一般是因爲Stop The World 階段。

    併發(Concurrent):用戶線程與垃圾收集器線程同時執行,不必定是並行執行,多是交替執行(競爭)

    增量:若是一個階段是增量的,那麼它能夠運行一段時間以後因爲某些條件提早終止,例如須要執行更高優先級的GC 階段,同時仍然完成生產性工做。增量階段與須要徹底完成的階段造成鮮明對比。

5、垃圾收集器 

知道了算法以後,天然而然咱們到了JVM中對這些算法的實現和應用,即各類垃圾收集器(Garbage Collector)。

首先要認識到的一個重要方面是,對於大多數JVM,須要兩種不一樣的GC算法,一種用於清理新生代,另外一種用於清理老年代。

意思就是說,在JVM中你一般會看到兩種收集器組合使用。下圖是JVM 中全部的收集器(Java 8 ),其中有連線的就是能夠組合的。

爲了減少複雜性,快速記憶,我這邊直接給出比較經常使用的幾種組合。其餘的要麼是已經廢棄了要麼就是在現實狀況下不實用的。

接下去咱們開始具體介紹上各個垃圾收集器。這裏須要提一下的是,我這邊是將垃圾收集器分紅如下幾類來說述的:

  • Serial GC
  • Parallel GC
  • Concurrent Mark and Sweep (CMS)
  • G1 - Garbage First

理由無他,我以爲這樣更符合理解的思路,你更好理解。

關注微信公衆號:Java技術棧,在後臺回覆:JVM,能夠獲取我整理的 N 篇最新JVM 教程,都是乾貨。

4.1 串行收集器

Serial 翻譯過來能夠理解成單線程。單線程收集器有Serial 和 Serial Old 兩種,它們的惟一區別就是:Serial 工做在新生代,使用「複製」算法,Serial Old 工做在老年代,使用「標誌-整理」算法。因此這裏將它們放在一塊兒講。

串行收集器收集器是最經典、最基礎,也是最好理解的。它們的特色就是單線程運行及獨佔式運行,所以會帶來很很差的用戶體驗。雖然它的收集方式對程序的運行並不友好,但因爲它的單線程執行特性,應用於單個CPU硬件平臺的性能能夠超過其餘的並行或併發處理器。

「單線程」的意義並不只僅是說明它只會使用一個處理器或一條收集線程去完成垃圾收集工做,更重要的是強調在它進行垃圾收集時,必須暫停其餘全部工做線程,直到它收集結束(STW階段)。

STW 會帶給用戶惡劣的體驗,因此從JDK 1.3開始,一直到如今最新的JDK 13,HotSpot虛擬機開發團隊爲消除或者下降用戶線程因垃圾收集而致使停頓的努力一直持續進行着,從Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最終至如今垃圾收集器的最前沿成果Shenandoah和ZGC等。

雖然新的收集器不少,可是串行收集器仍有其適合的場景。迄今爲止,它依然是HotSpot虛擬機運行在客戶端模式下的默認新生代收集器,有着優於其餘收集器的地方,那就是簡單而高效。對於內存資源受限的環境,它是全部收集器裏額外內存消耗最小的,單線程沒有線程交互開銷。(這裏實際上也是一個時間換空間的概念)

經過JVM參數 -XX:+UseSerialGC 可使用串行垃圾回收器(上面表格也有說明)

推薦閱讀:46張PPT弄懂JVM、GC算法和性能調優!

4.2 並行收集器

按照程序發展的思路,單線程處理以後,下一步很天然就到了多核處理器時代,程序多線程並行處理的時代。並行收集器是多線程的收集器,在多核CPU下可以很好的提升收集性能。

這裏咱們會介紹:

  • ParNew
  • Parallel Scavenge
  • Parallel Old

這裏仍是提供太長不看版白話總結,方便理解。由於我知道有些人剛開始學習JVM 看這些名詞都會以爲頭暈。

  • ParNew收集器 就是 Serial收集器的多線程版本,基於「複製」算法,其餘方面徹底同樣,在JDK9以後差很少退出歷史舞臺,只能配合CMS在JVM中發揮做用。
  • Parallel Scavenge 收集器 和 ParNew收集器相似,基於「複製」算法,但前者更關注可控制的吞吐量,而且可以經過-XX:+UseAdaptiveSizePolicy打開垃圾收集自適應調節策略的開關。
  • Parallel Old 就是 Parallel Scavenge 收集器的老年代版本,基於「標記-整理」算法實現。

A. ParNew 收集器

ParNew收集器除了支持多線程並行收集以外,其餘與Serial收集器相比並無太多創新之處,但它倒是很多運行在服務端模式下的HotSpot虛擬機,尤爲是JDK 7以前的遺留系統中首選的新生代收集器,其中有一個與功能、性能無關但其實很重要的緣由是:除了Serial收集器外,目前只有它能與CMS收集器配合工做。

可是從G1 出來以後呢,ParNew的地位就變得微妙起來,自JDK 9開始,ParNew加CMS收集器的組合就再也不是官方推薦的服務端模式下的收集器解決方案了。官方但願它能徹底被G1所取代,甚至還取消了『ParNew + Serial Old』 以及『Serial + CMS』這兩組收集器組合的支持(其實本來也不多人這樣使用),並直接取消了-XX:+UseParNewGC參數,這意味着ParNew 和CMS 今後只能互相搭配使用,再也沒有其餘收集器可以和它們配合了。能夠理解爲今後之後,ParNew 合併入CMS,成爲它專門處理新生代的組成部分。

B. Parallel Scavenge收集器

Parallel Scavenge收集器與ParNew收集器相似,也是使用複製算法的並行的多線程新生代收集器。但Parallel Scavenge收集器關注可控制的吞吐量(Throughput)

注:吞吐量是指CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /( 運行用戶代碼時間 + 垃圾收集時間 )

Parallel Scavenge收集器提供了幾個參數用於精確控制吞吐量和停頓時間:

C. Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,多線程,基於「標記-整理」算法。這個收集器是在JDK 1.6中才開始提供的。

因爲若是新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外別無選擇(Parallel Scavenge沒法與CMS收集器配合工做),Parallel Old收集器的出現就是爲了解決這個問題。Parallel Scavenge和Parallel Old收集器的組合更適用於注重吞吐量以及CPU資源敏感的場合。

4.3 Concurrent Mark and Sweep (CMS)

CMS(Concurrent Mark Sweep,併發標記清除) 收集器是以獲取最短回收停頓時間爲目標的收集器(追求低停頓),它在垃圾收集時使得用戶線程和 GC 線程併發執行,所以在垃圾收集過程當中用戶也不會感到明顯的卡頓。

從名字就能夠知道,CMS是基於「標記-清除」算法實現的。它的工做過程相對於上面幾種收集器來講,就會複雜一點。整個過程分爲如下四步:

1)初始標記 (CMS initial mark):主要是標記 GC Root 開始的下級(注:僅下一級)對象,這個過程會 STW,可是跟 GC Root 直接關聯的下級對象不會不少,所以這個過程其實很快。

2)併發標記 (CMS concurrent mark):根據上一步的結果,繼續向下標識全部關聯的對象,直到這條鏈上的最盡頭。這個過程是多線程的,雖然耗時理論上會比較長,可是其它工做線程並不會阻塞,沒有 STW。

3)從新標記(CMS remark):顧名思義,就是要再標記一次。爲啥還要再標記一次?由於第 2 步並無阻塞其它工做線程,其它線程在標識過程當中,頗有可能會產生新的垃圾。

這裏舉一個很形象的例子:

就好比你和你的小夥伴(多個GC線程)給一條長走廊打算衛生,從一頭打掃到另外一頭。當大家打掃到走廊另外一頭的時候,可能有同窗(用戶線程)丟了新的垃圾。因此,爲了打掃乾淨走廊,須要你示意全部的同窗(用戶線程)別再丟了(進入STW階段),而後你和小夥伴迅速把剛剛的新垃圾收走。固然,由於剛纔已經收過一遍垃圾,因此此次收集新產生的垃圾,用不了多長時間(即:STW 時間不會很長)。

4)併發清除(CMS concurrent sweep):

❔❔❔ 提問環節:爲何CMS要使用「標記-清除」算法呢?剛纔咱們不是提到過「標記-清除」算法,會留下不少內存碎片嗎?

確實,可是也沒辦法,若是換成「標記 - 整理」算法,把垃圾清理後,剩下的對象也順便整理,會致使這些對象的內存地址發生變化,別忘了,此時其它線程還在工做,若是引用的對象地址變了,就天下大亂了。

推薦閱讀:46張PPT弄懂JVM、GC算法和性能調優!

對於上述的問題JVM提供了兩個參數:

另外,因爲最後一步併發清除時,並不阻塞其它線程,因此還有一個反作用,在清理的過程當中,仍然可能會有新垃圾對象產生,只能等到下一輪 GC,纔會被清理掉。

4.4 G1 - Garbage First

JDK 9發佈之日,G1宣告取代Parallel Scavenge加Parallel Old組合,成爲服務端模式下的默認垃圾收集器。

鑑於 CMS 的一些不足以外,好比: 老年代內存碎片化,STW 時間雖然已經改善了不少,可是仍然有提高空間。G1 就橫空出世了,它對於堆區的內存劃思路很新穎,有點算法中分治法「分而治之」的味道。具體什麼意思呢,讓咱們繼續看下去。

G1 將連續的Java堆劃分爲多個大小相等的獨立區域(Region),每個Region均可以根據須要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。每一個Region的大小能夠經過參數-XX:G1HeapRegionSize設定,取值範圍爲1MB~32MB,且應爲2的N次冪。

Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認爲只要大小超過了一個Region容量一半的對象便可斷定爲大對象。對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中。

Humongous,簡稱 H 區,是專用於存放超大對象的區域,一般 >= 1/2 Region Size,G1的大多數行爲都把Humongous Region做爲老年代的一部分來進行看待。

認識了G1中的內存規劃以後,咱們就能夠理解爲何它叫作"Garbage First"。全部的垃圾回收,都是基於 region 的。G1根據各個Region回收所得到的空間大小以及回收所需時間等指標在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大(垃圾)的Region,從而能夠有計劃地避免在整個Java堆中進行全區域的垃圾收集。這也是 "Garbage First" 得名的由來。

G1從總體來看是基於「標記-整理」算法實現的收集器,但從局部(兩個Region之間)上看又是基於「標記-複製」算法實現,不管如何,這兩種算法都意味着G1運做期間不會產生內存空間碎片,垃圾收集完成以後能提供規整的可用內存。這種特性有利於程序長時間運行,在程序爲大對象分配內存時不容易因沒法找到連續內存空間而提早觸發下一次GC。

❔❔❔ 提問環節:

一個對象和它內部所引用的對象可能不在同一個 Region 中,那麼當垃圾回收時,是否須要掃描整個堆內存才能完整地進行一次可達性分析?

這裏就須要引入 Remembered Set 的概念了。

答案是不須要,每一個 Region 都有一個 Remembered Set (記憶集),用於記錄本區域中全部對象引用的對象所在的區域,進行可達性分析時,只要在 GC Roots 中再加上 Remembered Set 便可防止對整個堆內存進行遍歷。

再提一個概念,Collection Set :簡稱 CSet,記錄了等待回收的 Region 集合,GC 時這些 Region 中的對象會被回收(copied or moved)。

G1 運做步驟

若是不計算維護 Remembered Set 的操做,G1 收集器的工做過程分爲如下幾個步驟:

  • 初始標記(Initial Marking):Stop The World,僅使用一條初始標記線程對全部與 GC Roots 直接關聯的對象進行標記。
  • 併發標記(Concurrent Marking):使用一條標記線程與用戶線程併發執行。此過程進行可達性分析,速度很慢。
  • 最終標記(Final Marking):Stop The World,使用多條標記線程併發執行。
  • 篩選回收(Live Data Counting and Evacuation):回收廢棄對象,此時也要 Stop The World,並使用多條篩選回收線程併發執行。(還會更新Region的統計數據,對各個Region的回收價值和成本進行排序)

從上述階段的描述能夠看出,G1收集器除了併發標記外,其他階段也是要徹底暫停用戶線程的,換言之,它並不是純粹地追求低延遲,官方給它設定的目標是在延遲可控的狀況下得到儘量高的吞吐量

G1 的 Minor GC/Young GC

在分配通常對象時,當全部eden region使用達到最大閾值而且沒法申請足夠內存時,會觸發一次YGC。每次YGC會回收全部Eden以及Survivor區,而且將存活對象複製到Old區以及另外一部分的Survivor區。

下面是一段通過抽取的GC日誌:

GC pause (G1 Evacuation Pause) (young)  ├── Parallel Time    ├── GC Worker Start    ├── Ext Root Scanning    ├── Update RS    ├── Scan RS    ├── Code Root Scanning    ├── Object Copy  ├── Code Root Fixup  ├── Code Root Purge  ├── Clear CT  ├── Other    ├── Choose CSet    ├── Ref Proc    ├── Ref Enq    ├── Redirty Cards    ├── Humongous Register    ├── Humongous Reclaim    ├── Free CSet

由這段GC日誌咱們可知,整個YGC由多個子任務以及嵌套子任務組成,且一些核心任務爲:Root Scanning,Update/Scan RS,Object Copy,CleanCT,Choose CSet,Ref Proc,Humongous Reclaim,Free CSet。

G1 的 Mixed GC

當愈來愈多的對象晉升到老年代Old Region 時,爲了不堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即Mixed GC,是收集整個新生代以及部分老年代的垃圾收集。除了回收整個Young Region,還會回收一部分的Old Region ,這裏須要注意:是一部分老年代,而不是所有老年代,能夠選擇哪些Old Region 進行收集,從而能夠對垃圾回收的耗時時間進行控制。

Mixed GC的整個子任務和YGC徹底同樣,只是回收的範圍不同。

注:G1 通常來講是沒有FGC的概念的。由於它自己不提供FGC的功能。

若是 Mixed GC 仍然效果不理想,跟不上新對象分配內存的需求,會使用 Serial Old GC 進行 Full GC強制收集整個 Heap。

相比CMS,G1總結有如下優勢:

G1運做期間不會產生內存空間碎片,垃圾收集完成以後能提供規整的可用內存。這種特性有利於程序長時間運行。

G1 能預測 GC 停頓時間, STW 時間可控(G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.)

關於G1實際上還有不少的細節能夠講,這裏但願讀者去閱讀《深刻理解Java虛擬機》或者其餘資料來延伸學習,查漏補缺。

相關參數:

本系列關於JVM 垃圾回收的知識就到這裏了。

由於篇幅的關係,也受限於能力水平,本文不少細節沒有涉及到,只能算是爲學習JVM的同窗打開了一扇的門(一扇和日常看到的文章相比要大那麼一點點的門,寫了這麼久容許我自戀一下吧😂😂)。但願不過癮的同窗能本身更加深刻的學習。

若是本文有幫助到你,但願能點個贊,這是對個人最大動力🤝🤝🤗🤗。
做者:Richard_Yi
https://juejin.im/post/5e151b...

推薦去個人博客閱讀更多:

1.Java JVM、集合、多線程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿里巴巴等大廠最新面試題

生活很美好,明天見~

相關文章
相關標籤/搜索