理解JVM(七):垃圾回收器

Joepis發表於2018-06-29

一些概念

並行(Parallel)

指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。

併發(Concurrent)

指使用者執行緒與垃圾收集執行緒同時執行(但不一定是並行的,可能會交替執行),使用者程式在繼續執行,而垃圾收集程式執行於另一個CPU上。

吞吐量

CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)。虛擬機器總共執行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

HotSpot虛擬機器的垃圾回收器

Serial

  • 最基本的單執行緒垃圾收集器。使用一個CPU或一條收集執行緒去執行垃圾收集工作。
  • 工作時會Stop The World,暫停所有使用者執行緒,造成卡頓。適合執行在Client模式下的虛擬機器。
  • 用作新生代收集器,複製演算法。

ParNew

  • Serial收集器的多執行緒版本,和Serial的唯一區別就是使用了多條執行緒去垃圾收集。
  • 除了Serial,只有它可以和CMS搭配使用的收集器。
  • 用作新生代收集器,複製演算法。

Parallel Scavenge

  • 用作新生代收集器,複製演算法。
  • 關注高吞吐量,可以高效率地利用CPU時間,儘快完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。
  • Parallel Scavenge收集器提供了兩個引數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis引數以及直接設定吞吐量大小的-XX:GCTimeRatio引數。

Serial Old

  • Serial收集器的老年代版本,單執行緒,標記-整理 演算法。
  • 一般用於Client模式的虛擬機器。
  • 當虛擬機器是Server模式時,有2個用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用 ,另一種用途就是作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。

Parallel Old

  • Parallel Scavenge收集器的老年代版本,使用多執行緒和 標記-整理 演算法。在JDK 1.6中開始提供。
  • 在注重吞吐量的場合,配合Parallel Scavenge收集器使用。

CMS(Concurrent Mark Sweep)

  • 一種以獲取最短回收停頓時間為目標的收集器。適合需要與使用者互動的程式,良好的響應速度能提升使用者體驗。
  • 基於 標記—清除 演算法。適合作為老年代收集器。
  • 收集過程分4步:
    • 初始標記(CMS initial mark):只是標記一下GC Roots能直接關聯到的物件,速度很快,會Stop The World
    • 併發標記(CMS concurrent mark):進行GC Roots Tracing(可達性分析)的過程。
    • 重新標記(CMS remark):會Stop The World。為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般比初始標記階段稍長些,但遠比並發標記的時間短。
    • 併發清除(CMS concurrent sweep):回收記憶體。
  • 耗時最長的併發標記和併發清除過程收集器執行緒都可以與使用者執行緒一起工作,所以時併發執行的。
  • 缺點:
    • 併發階段,雖然不會導致使用者執行緒暫停,但會佔用一部分執行緒(CPU資源),導致應用變慢,吞吐量降低。預設啟動收集執行緒數是(CPU數量+3)/4。即當CPU在4個以上時,併發回收時垃圾收集執行緒不少於25%的CPU資源,並且隨著CPU數量的增加而下降。但是當CPU不足4個(譬如2個)時,CMS對使用者程式的影響就可能變得很大。
    • 無法清除浮動垃圾。併發清除階段,使用者執行緒還在執行,還會產生新垃圾。這些垃圾不會在此次GC中被標記,只能等到下次GC被回收。
    • 標記-清除 演算法會產生大量不連續記憶體,導致分配大物件時記憶體不夠,提前觸發Full GC。

G1

  • 在JDK1.7提供的先進垃圾收集器。
  • 既適合新生代,也適合老年代。
  • 空間整合:使用 標記-整理 演算法,不產生碎片空間。
  • 整個Java堆被分為多個大小相同的的塊(region)。新生代和老年代不再是物理隔離的,而是一部分region塊組成的集合。
  • 預設把堆平均分成2048個region,最小1M,最大32M,必須是2的冪次方,可以通過-XX:G1HeapRegionSize引數指定。region分為4種:
    • E:eden區,新生代
    • S:survivor區,新生代
    • O:old區,老年代
    • H:humongous區,用來放大物件。當新建物件大小超過region大小一半時,直接在新的一個或多個連續region中分配,並標記為H
  • 可預測的停頓時間:估算每個region內的垃圾可回收的空間以及回收需要的時間(經驗值),記錄在一個優先列表中。收集時,優先回收價值最大的region,而不是在整個堆進行全區域回收。這樣提高了回收效率,得名:Garbage-First。
  • G1中有2種GC:
    • young GC:新生代eden區沒有足夠可用空間時觸發。存活的物件移到survivor區或晉升old區。
    • mixed GC:當old區物件很多時,老年代物件空間佔堆總空間的比值達到閾值(-XX:InitiatingHeapOccupancyPercent預設45%)會觸發,它除了回收年輕代,也回收 部分 老年代(回收價值高的部分region)。
  • mixed GC回收步驟:
    • 初始標記(Initial Marking):只是標記一下GC Roots能直接關聯到的物件,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段使用者程式併發執行時,能在正確可用的Region中建立新物件。這階段需要停頓執行緒(STW),但耗時很短,共用YGC的停頓,所以一般伴隨著YGC發生。
    • 併發標記(Concurrent Marking):進行可達性分析,找出存活物件,耗時長,但可與使用者執行緒併發執行。
    • 最終標記(Final Marking):修正併發標記階段使用者執行緒執行導致的變動記錄。會STW,但可以並行執行,時間不會很長。
    • 篩選回收(Live Data Counting and Evacuation):根據每個region的回收價值和回收成本排序,根據使用者配置的GC停頓時間開始回收。
  • 當物件分配過快,mixed GC來不及回收,G1會退化,觸發Full GC,它使用單執行緒的Serial收集器來回收,整個過程STW,要儘量避免這種情況。
  • 當記憶體很少的時候(存活物件佔用大量空間),沒有足夠空間來複制物件,會導致回收失敗。這時會保留被移動過的物件和沒移動的物件,只調整引用。失敗發生後,收集器認為存活物件被移動了,有足夠空間讓應用程式使用,於是使用者執行緒繼續工作,等待下一次觸發GC。如果記憶體不夠,就會觸發Full GC。

參考G1的詳細介紹

相關文章