java垃圾收集器 - JVM垃圾收集筆記整理

目錄

在談垃圾收集器前先簡單的大致瞭解下垃圾收集算法

標記清除算法

複製算法

標記整理算法/標記壓縮算法

分代算法

分區算法

Minor GC、Full GC觸發條件

不同的垃圾收集器

1. Serial收集器

2. ParNew收集器

3. Parallel Scavenge收集器

4. Serial Old收集器

5. Paralled Old收集器

6. CMS收集器

7. G1收集器


在談垃圾收集器前先簡單的大致瞭解下垃圾收集算法

  • 標記清除算法

       分爲兩個階段的動作。第一、標記所有從根節點開始可達的對象。第二、清除所有未標記的對象。缺點:產生大量不連續的內存碎片,空間碎片太多可能會導致後期JVM需要分配大對象時,無法找到足夠連續內存空間,從而再次觸發一次垃圾收集動作。

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

  • 複製算法

       需要兩塊內存空間,只使用其中一塊。開始回收的時候將已使用的一塊內存空間中存活對象複製到另一塊內存空間,並一次性回收這塊內存空間。缺點:會有一定的內存空間浪費

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

  • 標記整理算法/標記壓縮算法

       有點像是整合了標記清除算法和複製算法。標記可回收對象,然後複製所有存活對象像一端移動。最後直接清理掉邊界外的內存。

 

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

  • 分代算法

       內存區域根據對象存活週期劃分爲幾塊。新生代和老年代。新生代只有少量存活對象,使用複製算法。老年代因爲對象存活率較高,使用標記清理或者標記整理算法。

 

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

  • 分區算法

        按照對象生命週期長短劃分成兩個部分,將整個堆空間劃分成連續的不同小區間。每一個小區間(Region)都獨立使用,獨立回收。這種算法的好處是可以控制一次回收多少個小區間(Region)。從而減少(Stop-The-World時間STW)停頓時間。

學習來源:《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

Minor GC、Full GC觸發條件


Minor GC觸發條件:當Eden區滿時,觸發Minor GC。

Full GC觸發條件:

(1)調用System.gc時,系統建議執行Full GC,但是不必然執行

(2)老年代空間不足

(3)方法區空間不足

(4)通過Minor GC後進入老年代的平均大小大於老年代的可用內存

(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

內容來源:https://blog.csdn.net/u013630349/article/details/78342645

不同的垃圾收集器

1. Serial收集器

    新生代收集器,歷史悠久的垃圾收集器,曾經是JDK1.3.1之前新生代收集的唯一選擇。

是一個單線程的收集器。需要暫停其他所有工作線程(用戶操作線程也需要一併停止Stop-The-World,縮寫STW。本文後面會使用STW代表Stop-TheWorld),直到垃圾收集的結束。

使用-XX:+UseSerialGC參數可指定新生代Serial收集器和老年代Serial Old收集器。

client模式下默認

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

2. ParNew收集器

    新生代收集器,其實就是Serial收集器的多線程版本。在單CPU環境中不會比Serial收集器有更好的效果。

默認開啓線程數量與CPU數量相同,也可以使用-XX:ParalledGCThreads配置線程數量。

server模式下首選

開啓ParNew收集器可以使用參數:

  • -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(請不要在JDK1.8版本後續使用此參數)
  • -XX:+UseConcMarkSweepGC:新生代使用ParNew收集器,老年代使用CMS收集器。

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

注意:JDK8版本刪除了啓動ParNew收集器選項。-XX:+UseParNewGC。ParNew收集器將配合CMS收集器使用,因爲CMS收集器需要ParNew收集器。

學習來源:https://docs.oracle.com/javase/10/migrate/toc.htm#JSMIG-GUID-9E847B7E-1F6B-4AD4-A5EE-66F8EF8BA9F6

http://openjdk.java.net/jeps/214

3. Parallel Scavenge收集器

    新生代收集器,使用複製算法,多線程收集器(與Serial和ParNew一樣需要STW),

可控吞吐量的垃圾收集器。此垃圾收集器無法與CMS配合使用。

吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。

1)-XX:MaxGCPauseMillis參與值是一個大於0的毫秒數。收集器儘可能的保證內存回收花費時間不超過設定值。

    (JDK11默認值200)

2)-XX:GCTimeRatio參數值是一個大於0且小於100的整數。用來配置垃圾收集時間佔總時間的比率,相當於是吞吐量的倒數。(1/1+N)

     例如:-XX:GCTimeRatio=19

                1/(1+19)=0.05。即5%。

    書中所寫默認值爲99

     (JDK11默認值12)

3)-XX:UseAdaptiveSizePolicy開關參數。默認開啓。開關打開後就不需要手工指定新生代的大小(-Xmm)、Eden與Servivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數。虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式成爲GC自適應的調劑策略。這也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。

(JDK 1.8 默認使用 UseParallelGC 垃圾回收器,該垃圾回收器默認啓動了 AdaptiveSizePolicy)

啓動此收集器可使用參數:

  • -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。
  • -XX:+UseParallelOldGC:新生代使用ParallelGC收集器,老年代使用ParallelOldGC收集器。

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

4. Serial Old收集器

    Serial收集器的老年代版本。採用標記整理算法。

主要在client模式下使用。

若要啓動Serial Old收集器可以嘗試一下參數:

  • -XX:+UseSerialGC:新生代、老年代都使用Serial收集器。
  • -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(請不要在JDK8以及後續版本使用此參數,詳見ParNew收集器)
  • -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。

如果在server模式下有兩大用途。一種是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用。另一種用途就是作爲CMS收集器的後備預案,在併發收集發生Conncurrent Mode Failure時使用。

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

5. Paralled Old收集器

    此收集器是Parallel Scavenge收集器的老年代版本。使用標記整理算法。JDK1.6開始提供。

可以使用參數-XX:+UseParallelOldGC開啓此收集器。新生代使用Parallel收集器,老年代使用ParallelOldGC收集器。

學習來源:《深入理解Java虛擬機》-- JDK1.7,《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

6. CMS收集器

    老年代收集器。是一種獲取最短停頓時間爲目標的收集器。通過標記清除算法實現

回收步驟分爲:

    1)初始標記(CMS initial mark)--STW

          標記GC Roots能夠直接關聯到的對象

    2)  併發標記(CMS concurrent mark)

         GC Roots Tracing過程

    3)預清理

         清理前準備以及控制停頓時間

         默認情況下這個預清理步驟是存在的。也可以通過參數-XX:-CMSPrecleaningEnabled來關閉預清理。

    4)重新標記(CMS remark)--STW

          修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄

    5)  併發清除(CMS concurrent sweep)

缺點:

    1)CMS收集器對CPU資源非常敏感,由於CMS的併發標記和併發清除會佔用一部分線程資源或CPU資源而導致程序變慢、總吞吐量降低。CMS默認啓動的回收線程數是(CPU數量+3/4)。當CPU在4個以上時垃圾收集線程不少於25%的CPU資源,並隨着CPU數量的增加而下降。當CPU不足4個時很可能會導致用戶程序執行速度忽然降到50%。

    2)CMS收集器無法處理浮動垃圾。由於併發清理階段用戶線程還在運行着,所以會導致會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後。CMS無法在當次收集中處理掉它們,只好留在下一次GC時清理。這一部分垃圾就成爲“浮動垃圾”。可以適當調高CMSInitiatingOccupancyFraction的值來提高觸發百分比。以便降低內存回收次數。如果CMS運行期間預留內存無法滿足程序需要,就會觸發一次“Concurrent Mode Failure”失敗。這時虛擬機將啓動後備預案:臨時啓動Serial Old收集器來重新進行老年代的垃圾收集工作。

    3)CMS收集器是一款基於標記清除算法實現的收集器,垃圾收集工作結束後會有大量空間碎片產生。很可能會導致老年代還有很大空間,但是無法找到足夠大的連續空間來分配當前對象,從而不得不觸發一次Full GC。CMS收集器提供了一個+UseCMSCompactAtFullCollection開關參數,用於在CMS收集器頂不住需要機型Full GC的時候開啓內存碎片的合併整理過程。內存整理過程無法併發執行。虛擬機還提供了另一個參數-XX:CMSFullGCsBeforeCompaction用於設置執行多少次不壓縮的Full GC後,跟着來一次帶壓縮的。

(值得注意的是-XX:+UseCMSCompactAtFullCollection,-XX:+CMSFullGCsBeforeCompaction 這兩個參數根據目前瞭解到,JDK8中還在使用,但是在JDK9中已經刪除,如果在JDK9中還在使用則會導致jvm啓動失敗:“Unrecognized VM option”)

--XX:UseConcMarkSweepGC開關參數。用於開啓和關閉CMS收集器

--XX:CMSInitiatingOccupancyFraction是一個0~100之間的整數。用來標記老年代使用比例達到某個比例時觸發CMS垃圾收集器。JDK1.5默認值68。表示老年代使用空間達到68%時CMS垃圾收集器會被激活。如果該值設置爲-1的話,則由另外兩個參數決定啓動CMS收集器MinHeapFreeRatio、CMSTriggerRatio。((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 。

 

學習來源:《深入理解Java虛擬機》-- JDK1.7

學習來源:《實戰Java虛擬機:JVM故障診斷與性能優化》--JDK1.8

學習來源:https://www.jianshu.com/p/61bf0e9011c4

GC Roots解釋

GC管理的主要區域是Java堆,一般情況下只針對堆進行垃圾回收。方法區、棧和本地方法區不被GC所管理,因而選擇這些區域內的對象作爲GC roots,被GC roots引用的對象不被GC回收。

GC Root

常說的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的對象,GC會收集那些不是GC roots且沒有被GC roots引用的對象。

一個對象可以屬於多個root,GC root有幾下種:

Class - 由系統類加載器(system class loader)加載的對象,這些類是不能夠被回收的,他們可以以靜態字段的方式保存持有其它對象。我們需要注意的一點就是,通過用戶自定義的類加載器加載的類,除非相應的java.lang.Class實例以其它的某種(或多種)方式成爲roots,否則它們並不是roots。

Thread - 活着的線程

Stack Local - Java方法的local變量或參數

JNI Local - JNI方法的local變量或參數

JNI Global - 全局JNI引用

Monitor Used - 用於同步的監控對象

Held by JVM - 用於JVM特殊目的由GC保留的對象,但實際上這個與JVM的實現是有關的。可能已知的一些類型是:系統類加載器、一些JVM知道的重要的異常類、一些用於處理異常的預分配對象以及一些自定義的類加載器等。然而,JVM並沒有爲這些對象提供其它的信息,因此需要去確定哪些是屬於"JVM持有"的了。

內容來源:https://www.zhihu.com/question/50381439/answer/226231622

7. G1收集器

    JDK1.7u4開始商用提供。被sun定義爲替代JDK1.5中發佈的CMS收集器的收集器。他可以管理整個GC堆。採用分區算法。其他收集器的收集範圍爲整個新生代或者老年代,而G1收集器不同的是,雖然還保留新生代和老年代的概念,它將堆劃分爲多個大小相等的獨立區域(Region),新生代和老年代不再物理隔離,而是作爲一部分Region的集合。G1在後臺維護者一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region。G1中每個對應Region都有一個與之對應的Remembered Set用來避免全堆掃描,當程序發現對Reference類型數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用對象是否處於不同Region之中,如果是,便通過CardTable把相關引用信息記錄到被引用對象所屬Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

對於G1收集器此處只做簡單介紹。更詳細的介紹,請參見:https://blog.csdn.net/jl19861101/article/details/88566316

G1收集器的特性:

    併發並行:可以利用多個CPU或CPU核心來縮短STW時間。部分其他收集器原本需要停頓用戶線程的GC動作,G1可以通過併發的方式讓用戶線程繼續執行。

    分代收集:採用不同的方式處理新創建的對象和已經存活了一段時間和熬過多次GC的舊對象。

    空間整合:從整體上看與CMS的標記清理算法相同,但從局部(不同的Region之間)來看是基於複製算法實現的。這意味着G1運作期間不會產生內存空間碎片。

    可預測的停頓:降低停頓時間(STW)是G1相對於CMS的一大優勢。可以明確指出一個長度爲M毫秒的時間片段內。

如果不計算維護Remembered Set操作,G1收集器的運作步驟爲:

    1)初始標記(Initial Marking)----標記GC Roots可以直接關聯對象,並修改TAMS(Next Top at Mark Start)值,讓下一階段併發標記運行時,能在正確可用的 Region中創建新對象。需要STW。這個階段會伴隨着Minor GC。

    2)Root區域掃描(Root Region Scanning)----由於初始標記階段必然會伴隨一次Minor GC,所以在初始化標記後,eden被清空。並且存貨對象被移入survivor區域。在這個階段,將掃描survivor區域直接可達的老年代區域,並標記這些直接可達的對象,這個階段是併發執行的。但是不能同Minor GC同時執行。

    3)併發標記(Concurrent Marking)----從GC Roots開始對堆中對象進行可達性分析找出存活對象。

    4)最終標記(Final Marking)----用來修正併發標記期間用戶線程持續運行導致的標記變動的那一部分標記記錄。虛擬機將這段時間對象變化記錄在線程Remembered Set Log裏面。最終標記階段把Remembered Set Log的數據合併到Remembered Set Log中。需要STW。但是並行執行。

    5)篩選回收(Live Data Counting and Evacuation)----對各個Region的回收價值和成本進行排序,根據用戶希望的GC停頓時間來定製回收計劃。可併發執行。只回收一部分Region。時間是用戶可控制的。

參數:

-XX:+-UseG1GC:開關參數。用來開啓和關閉G1收集器。

學習來源:《深入理解Java虛擬機》-- JDK1.7

111

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章