jvm各個垃圾收集器介紹

1:心血來潮

今天吃完飯,吃撐了,也不想寫代碼,就寫着寫一篇博客。就來寫一個jvm垃圾收集器相關的吧
java

2:概述

JVM規範對於垃圾收集器的應該如何實現沒有任何規定,所以不一樣的廠商、不一樣版本的虛擬機所提供的垃圾收集器差異較大,這裏只看HotSpot虛擬機。 就像沒有最好的算法同樣,垃圾收集器也沒有最好,只有最合適。咱們能作的就是根據具體的應用場景選擇最合適的垃圾收集器。算法

3:Serial收集器

Serial(串行)收集器收集器是最基本、歷史最悠久的垃圾收集器了(新生代採用複製算法,老生代採用標誌整理算法)。你們看名字就知道這個收集器是一個單線程收集器了。 它的 「單線程」 的意義不只僅意味着它只會使用一條垃圾收集線程去完成垃圾收集工做,更重要的是它在進行垃圾收集工做的時候必須暫停其餘全部的工做線程( "Stop The World" :將用戶正常工做的線程所有暫停掉),直到它收集結束。 看圖理解:瀏覽器

上圖中:服務器

  • 新生代採用複製算法,Stop-The-World
  • 老年代採用標記-整理算法,Stop-The-World

當它進行GC工做的時候,雖然會形成Stop-The-World,正如每種算法都有存在的緣由,該串行收集器也有存在的緣由:由於簡單而高效(與其餘收集器的單線程比),對於限定單個CPU的環境來講,沒有線程交互的開銷,專心作GC,天然能夠得到最高的單線程效率。因此Serial收集器對於運行在client模式下的應用是一個很好的選擇(到目前爲止,它依然是虛擬機運行在client模式下的默認新生代收集器) 串行收集器的缺點很明顯,虛擬機的開發者固然也是知道這個缺點的,因此一直都在縮減Stop The World的時間。 在後續的垃圾收集器設計中停頓時間在不斷縮短(可是仍然還有停頓,尋找最優秀的垃圾收集器的過程仍然在繼續)多線程

特色

  • 針對新生代的收集器;
  • 採用複製算法;
  • 單線程收集;
  • 進行垃圾收集時,必須暫停全部工做線程,直到完成; 即會"Stop The World";

應用場景

  • 依然是HotSpot在Client模式下默認的新生代收集器;
  • 也有優於其餘收集器的地方: 簡單高效(與其餘收集器的單線程相比);
  • 對於限定單個CPU的環境來講,Serial收集器沒有線程交互(切換)開銷,能夠得到最高的單線程收集效率;
  • 在用戶的桌面應用場景中,可用內存通常不大(幾十M至一兩百M),能夠在較短期內完成垃圾收集(幾十MS至一百多MS),只要不頻繁發生,這是能夠接受的

設置參數

添加該參數來顯式的使用串行垃圾收集器: "-XX:+UseSerialGC"閉包

4:ParNew收集器(Serial收集器的多線程版本-使用多條線程進行GC)

ParNew收集器其實就是Serial收集器的多線程版本,除了使用多線程進行垃圾收集外,其他行爲(控制參數、收集算法、回收策略等等)和Serial收集器徹底同樣。 它是許多運行在Server模式下的虛擬機的首要選擇,除了Serial收集器外,目前只有它能與CMS收集器配合工做。 CMS收集器是一個被認爲具備劃時代意義的併發收集器,所以若是有一個垃圾收集器能和它一塊兒搭配使用讓其更加完美,那這個收集器必然也是一個不可或缺的部分了。 收集器的運行過程以下圖所示: 併發

特色

  • 除了多線程外,其他的行爲、特色和Serial收集器同樣;
  • 如Serial收集器可用控制參數、收集算法、Stop The World、內存分配規則、回收策略等;
  • Serial收集器共用了很多代碼;

應用場景:

在Server模式下,ParNew收集器是一個很是重要的收集器,由於除Serial外,目前只有它能與CMS收集器配合工做; 但在單個CPU環境中,不會比Serail收集器有更好的效果,由於存在線程交互開銷。框架

設置參數

指定使用CMS後,會默認使用ParNew做爲新生代收集: "-XX:+UseConcMarkSweepGC" 強制指定使用ParNew:
"-XX:+UseParNewGC" 指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相: "-XX:ParallelGCThreads"jvm

爲何只有ParNew能與CMS收集器配合

  • CMS是HotSpot在JDK1.5推出的第一款真正意義上的併發(Concurrent)收集器,第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做;
  • CMS做爲老年代收集器,但卻沒法與JDK1.4已經存在的新生代收集器Parallel Scavenge配合工做;
  • 由於Parallel Scavenge(以及G1)都沒有使用傳統的GC收集器代碼框架,而另外獨立實現;而其他幾種收集器則共用了部分的框架代碼;

5:Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,它也是使用複製算法的收集器,又是並行的多線程收集器。 Parallel Scavenge收集器關注點是吞吐量(如何高效率的利用CPU)。 CMS等垃圾收集器的關注點更多的是用戶線程的停頓時間(提升用戶體驗)。 所謂吞吐量就是CPU中用於運行用戶代碼的時間與CPU總消耗時間的比值。(吞吐量:CPU用於用戶代碼的時間/CPU總消耗時間的比值,即=運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)。好比,虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。) 運行示意圖: 佈局

Parallel Scavenge收集器提供了不少參數供用戶找到最合適的停頓時間或最大吞吐量,若是對於收集器運做不太瞭解的話,不進行手工優化,能夠選擇把內存管理優化交給虛擬機去完成。

特色

  • 新生代收集器;
  • 採用複製算法;
  • 多線程收集;
  • CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間;而Parallel Scavenge收集器的目標則是達一個可控制的吞吐量(Throughput);

應用場景

  • 高吞吐量爲目標,即減小垃圾收集時間,讓用戶代碼得到更長的運行時間;
  • 當應用程序運行在具備多個CPU上,對暫停時間沒有特別高的要求時,即程序主要在後臺進行計算,而不須要與用戶進行太多交互;
  • 例如,那些執行批量處理、訂單處理(對帳等)、工資支付、科學計算的應用程序;

設置參數

Parallel Scavenge收集器提供兩個參數用於精確控制吞吐量:

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

  • 控制最大垃圾收集停頓時間,大於0的毫秒數; MaxGCPauseMillis設置得稍小,停頓時間可能會縮短,但也可能會使得吞吐量降低;由於可能致使垃圾收集發生得更頻繁; 設置垃圾收集時間佔總時間的比率 "-XX:GCTimeRatio"

  • 設置垃圾收集時間佔總時間的比率,0 < n < 100的整數; GCTimeRatio至關於設置吞吐量大小; 垃圾收集執行時間佔應用程序執行時間的比例的計算方法是: 1 / (1 + n) 。 例如,選項-XX:GCTimeRatio=19,設置了垃圾收集時間佔總時間的5% = 1/(1+19);默認值是1% = 1/(1+99),即n=99; 垃圾收集所花費的時間是年輕一代和老年代收集的總時間; 若是沒有知足吞吐量目標,則增長代的內存大小以儘可能增長用戶程序運行的時間;

6:Serial Old收集器

Serial收集器的老年代版本,它一樣是一個單線程收集器。 它主要有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種用途是做爲CMS收集器的後備方案

特色

  • 針對老年代;
  • 採用"標記-整理-壓縮"算法(Mark-Sweep-Compact);
  • 單線程收集;

應用場景

  • 主要用於Client模式;
  • 而在Server模式有兩大用途:
    (A)、在JDK1.5及以前,與Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配Parallel Scavenge收集器);
    (B)、做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用;

7:Parallel Old收集器

Parallel Scavenge收集器的老年代版本。 使用多線程和「標記-整理」算法。在注重吞吐量以及CPU資源的場合,均可以優先考慮 Parallel Scavenge收集器和Parallel Old收集器。 在JDK1.6纔有的。

特色

  • 針對老年代;
  • 採用"標記-整理-壓縮"算法;
  • 多線程收集; Parallel Scavenge/Parallel Old收集器運行示意圖以下:

應用場景

  • JDK1.6及以後用來代替老年代的Serial Old收集器;
  • 特別是在Server模式,多CPU的狀況下; 這樣在注重吞吐量以及CPU資源敏感的場景,就有了Parallel Scavenge(新生代)加Parallel Old(老年代)收集器的"給力"應用組合;

設置參數

指定使用Parallel Old收集器: "-XX:+UseParallelOldGC"

8:CMS(Concurrent Mark Sweep)收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它很是適合在注重用戶體驗的應用上使用。

特色

  • 針對老年代
  • 基於"標記-清除"算法(不進行壓縮操做,會產生內存碎片)
  • 以獲取最短回收停頓時間爲目標
  • 併發收集、低停頓
  • 須要更多的內存 CMS是HotSpot在JDK1.5推出的第一款真正意義上的併發(Concurrent)收集器; 第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做;

應用場景

  • 與用戶交互較多的場景;(如常見WEB、B/S-瀏覽器/服務器模式系統的服務器上的應用)
  • 但願系統停頓時間最短,注重服務的響應速度; 以給用戶帶來較好的體驗;

CMS收集器運做過程

從名字中的Mark Sweep這兩個詞能夠看出,CMS收集器是一種 「標記-清除」算法實現的,它的運做過程相比於前面幾種垃圾收集器來講更加複雜一些。整個過程可分爲四個步驟:

  • 初始標記: 暫停全部的其餘線程,初始標記僅僅標記GC Roots能直接關聯到的對象,速度很快;
  • 併發標記 併發標記就是進行GC Roots Tracing的過程; 同時開啓GC和用戶線程,用一個閉包結構去記錄可達對象。但在這個階段結束,這個閉包結構並不能保證包含當前全部的可達對象。由於用戶線程可能會不斷的更新引用域,因此GC線程沒法保證可達性分析的實時性。因此這個算法裏會跟蹤記錄這些發生引用更新的地方;
  • 從新標記: 從新標記階段就是爲了修正併發標記期間由於用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄(採用多線程並行執行來提高效率);須要"Stop The World",且停頓時間比初始標記稍長,但遠比並發標記短;
  • 併發清除: 開啓用戶線程,同時GC線程開始對爲標記的區域作清掃,回收全部的垃圾對象; 因爲整個過程耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做。 因此整體來講,CMS的內存回收是與用戶線程一塊兒「併發」執行的。 CMS收集器運行示意圖以下:

設置參數 指定使用CMS收集器 "-XX:+UseConcMarkSweepGC"

缺點

(一)對CPU資源敏感

面向併發設計的程序都對CPU資源比較敏感(併發程序的特色)。在併發階段,它雖然不會致使用戶線程停頓,但會由於佔用了一部分線程(或者說CPU資源)而致使應用程序變慢,總吞吐量會下降。(在對帳系統中,不適合使用CMS收集器)。 CMS的默認收集線程數量是=(CPU數量+3)/4; 當CPU數量越多,回收的線程佔用CPU就少。 也就是當CPU在4個以上時,併發回收時垃圾收集線程很多於25%的CPU資源,對用戶程序影響可能較大;不足4個時,影響更大,可能沒法接受。(好比 CPU=2時,那麼就啓動一個線程回收,佔了50%的CPU資源。) (一個回收線程會在回收期間一直佔用CPU資源)

  • 針對這種狀況,曾出現了"增量式併發收集器"(Incremental Concurrent Mark Sweep/i-CMS); 相似使用搶佔式來模擬多任務機制的思想,讓收集線程和用戶線程交替運行,減小收集線程運行時間; 但效果並不理想,JDK1.6後就官方再也不提倡用戶使用。

(二)沒法處理浮動垃圾

沒法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗 在併發清除時,用戶線程新產生的垃圾,稱爲浮動垃圾;

  • 解決辦法 這使得併發清除時須要預留必定的內存空間,不能像其餘收集器在老年代幾乎填滿再進行收集; 也能夠認爲CMS所須要的空間比其餘垃圾收集器大; 可使用"-XX:CMSInitiatingOccupancyFraction",設置CMS預留老年代內存空間; (詳解見名詞解釋)

(三)產生大量內存碎片

因爲CMS是基於「標記+清除」算法來回收老年代對象的,所以長時間運行後會產生大量的空間碎片問題,可能致使新生代對象晉升到老生代失敗。 因爲碎片過多,將會給大對象的分配帶來麻煩。所以會出現這樣的狀況,老年代還有不少剩餘的空間,可是找不到連續的空間來分配當前對象,這樣不得不提早觸發一次Full GC。

  • 解決辦法 使用"-XX:+UseCMSCompactAtFullCollection"和"-XX:+CMSFullGCsBeforeCompaction",須要結合使用。
  • UseCMSCompactAtFullCollection "-XX:+UseCMSCompactAtFullCollection"

爲了解決空間碎片問題,CMS收集器提供−XX:+UseCMSCompactAlFullCollection標誌,使得CMS出現上面這種狀況時不進行Full GC,而開啓內存碎片的合併整理過程; 但合併整理過程沒法併發,停頓時間會變長; 默認開啓(但不會進行,須要結合CMSFullGCsBeforeCompaction使用);

  • CMSFullGCsBeforeCompaction 因爲合併整理是沒法併發執行的,空間碎片問題沒有了,可是有致使了連續的停頓。所以,可使用另外一個參數−XX:CMSFullGCsBeforeCompaction,表示在多少次不壓縮的Full GC以後,對空間碎片進行壓縮整理。 能夠減小合併整理過程的停頓時間; 默認爲0,也就是說每次都執行Full GC,不會進行壓縮整理; 因爲空間再也不連續,CMS須要使用可用"空閒列表"內存分配方式,這比簡單實用"碰撞指針"分配內存消耗大;

CMS&Parallel Old

整體來看,CMS與Parallel Old垃圾收集器相比,CMS減小了執行老年代垃圾收集時應用暫停的時間; 但卻增長了新生代垃圾收集時應用暫停的時間、下降了吞吐量並且須要佔用更大的堆空間; (緣由:CMS不進行內存空間整理節省了時間,可是可用空間再也不是連續的了,垃圾收集也不能簡單的使用指針指向下一次可用來爲對象分配內存的地址了。相反,這種狀況下,須要使用可用空間列表。即,會建立一個指向未分配區域的列表,每次爲對象分配內存時,會從列表中找到一個合適大小的內存區域來爲新對象分配內存。這樣作的結果是,老年代上的內存的分配比簡單實用碰撞指針分配內存消耗大。這也會增長年輕代垃圾收集的額外負擔,由於老年代中的大部分對象是在新生代垃圾收集的時候重新生代提高爲老年代的。) 當新生代對象沒法分配過大對象,就會放到老年代進行分配。

9:G1收集器

上一代的垃圾收集器(串行serial, 並行parallel, 以及CMS)都把堆內存劃分爲固定大小的三個部分: 年輕代(young generation), 年老代(old generation), 以及持久代(permanent generation)。

注:堆內存中均可以認爲是Java對象。

G1(Garbage-First)是JDK7-u4才推出商用的收集器;
G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器。以極高機率知足GC停頓時間要求的同時,還具有高吞吐量性能特徵。被視爲JDK1.7中HotSpot虛擬機的一個重要進化特徵。
G1的使命是在將來替換CMS,而且在JDK1.9已經成爲默認的收集器。

特色

  • 並行與併發

G1能充分利用CPU、多核環境下的硬件優點,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間。部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓java程序繼續執行。

  • 分代收集

雖然G1能夠不須要其餘收集器配合就能獨立管理整個GC堆,可是仍是保留了分代的概念。

  • 能獨立管理整個GC堆(新生代和老年代),而不須要與其餘收集器搭配;
  • 可以採用不一樣方式處理不一樣時期的對象;
  • 雖然保留分代概念,但Java堆的內存佈局有很大差異;
  • 將整個堆劃分爲多個大小相等的獨立區域(Region);
  • 新生代和老年代再也不是物理隔離,它們都是一部分Region(不須要連續)的集合;

空間整合

與CMS的「標記--清理」算法不一樣,G1從總體來看是基於「標記整理」算法實現的收集器;從局部上來看是基於「複製」算法實現的。

  • 從總體看,是基於標記-整理算法;
  • 從局部(兩個Region間)看,是基於複製算法;
    這是一種相似火車算法的實現;
    不會產生內存碎片,有利於長時間運行;

(火車算法是分代收集器所用的算法,目的是在成熟對象空間中提供限定時間的漸進收集。在後面一篇中會專門介紹)

可預測的停頓

這是G1相對於CMS的另外一個大優點,下降停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能創建可預測的停頓時間模型。能夠明確指定M毫秒時間片內,垃圾收集消耗的時間不超過N毫秒。在低停頓的同時實現高吞吐量。

問題

爲何G1能夠實現可預測停頓

能夠有計劃地避免在Java堆的進行全區域的垃圾收集; G1收集器將內存分大小相等的獨立區域(Region),新生代和老年代概念保留,可是已經再也不物理隔離。 G1跟蹤各個Region得到其收集價值大小,在後臺維護一個優先列表; 每次根據容許的收集時間,優先回收價值最大的Region(名稱Garbage-First的由來); 這就保證了在有限的時間內能夠獲取儘量高的收集效率;

一個對象被不一樣區域引用的問題

一個Region不多是孤立的,一個Region中的對象可能被其餘任意Region中對象引用,判斷對象存活時,是否須要掃描整個Java堆才能保證準確? 在其餘的分代收集器,也存在這樣的問題(而G1更突出):回收新生代也不得不一樣時掃描老年代? 這樣的話會下降Minor GC的效率;

解決方法:

不管G1仍是其餘分代收集器,JVM都是使用Remembered Set來避免全局掃描: 每一個Region都有一個對應的Remembered Set; 每次Reference類型數據寫操做時,都會產生一個Write Barrier暫時中斷操做; 而後檢查將要寫入的引用指向的對象是否和該Reference類型數據在不一樣的Region(其餘收集器:檢查老年代對象是否引用了新生代對象); 若是不一樣,經過CardTable把相關引用信息記錄到引用指向對象的所在Region對應的Remembered Set中; 當進行垃圾收集時,在GC根節點的枚舉範圍加入Remembered Set; 就能夠保證不進行全局掃描,也不會有遺漏。

應用場景

  • 面向服務端應用,針對具備大內存、多處理器的機器;
  • 最主要的應用是爲須要低GC延遲,並具備大堆的應用程序提供解決方案; 如:在堆大小約6GB或更大時,可預測的暫停時間能夠低於0.5秒; (實踐:對帳系統中將CMS垃圾收集器修改成G1,下降對帳時間20秒以上)

具體什麼狀況下應用G1垃圾收集器比CMS好,能夠參考如下幾點(但不是絕對): 超過50%的Java堆被活動數據佔用; 對象分配頻率或年代的提高頻率變化很大; GC停頓時間過長(長於0.5至1秒); 建議: 若是如今採用的收集器沒有出現問題,不用急着去選擇G1; 若是應用程序追求低停頓,能夠嘗試選擇G1; 是否代替CMS只有須要實際場景測試才知道。(若是使用G1後發現性能尚未使用CMS好,那麼仍是選擇CMS比較好)

設置參數

能夠經過下面的參數,來設置一些G1相關的配置。 指定使用G1收集器: "-XX:+UseG1GC"

當整個Java堆的佔用率達到參數值時,開始併發標記階段;默認爲45: "-XX:InitiatingHeapOccupancyPercent"

爲G1設置暫停時間目標,默認值爲200毫秒: "-XX:MaxGCPauseMillis"

設置每一個Region大小,範圍1MB到32MB;目標是在最小Java堆時能夠擁有約2048個Region: "-XX:G1HeapRegionSize"

新生代最小值,默認值5%: "-XX:G1NewSizePercent"

新生代最大值,默認值60%: "-XX:G1MaxNewSizePercent"

設置STW期間,並行GC線程數: "-XX:ParallelGCThreads"

設置併發標記階段,並行執行的線程數: "-XX:ConcGCThreads"

G1在標記過程當中,每一個區域的對象活性都被計算,在回收時候,就能夠根據用戶設置的停頓時間,選擇活性較低的區域收集,這樣既能保證垃圾回收,又能保證停頓時間,並且也不會下降太多的吞吐量。Remark(從新標記)階段新算法的運用,以及收集過程當中的壓縮,都彌補了CMS不足。 引用Oracle官網的一句話:「G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)」。 G1計劃做爲併發標記-清除收集器(CMS)的長期替代品

相關文章
相關標籤/搜索