HotSpot 垃圾回收算法實現

《深刻理解Java虛擬機:JVM高級特性與最佳實踐》-筆記 算法

垃圾回收算法

枚舉根結點

一致性

在可達性分析期間整個系統看起來就像被凍結在某個時間點上,不能夠出現分析過程當中對象引用關係還在不斷變化的狀況。 安全

一致性要求致使GC進行時必須停頓全部Java執行線程。(Stop The World)
即便在號稱不會發生停頓的CMS收集器中,枚舉根節點時也是必須停頓的。 數據結構

HotSpot使用的是準確式GC,當執行系統停頓下來後,並不須要一個不漏地檢查完全部執行上下文和全局的引用位置,這是經過一組稱爲OopMap的數據結構來達到的。 多線程

在類加載完成後,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來;在JIT編譯過程當中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。
併發

安全點(Safe Point)

程序只有在到達安全點時才能暫停。安全點的選定標準是「是否具備讓程序長時間執行的特徵」。「長時間執行」的最明顯特徵就是指令序列的複用,如方法調用、循環跳轉等,具備這些功能的指令纔會產生安全點。 spa

讓程序暫停的兩種方式

  • 搶先式中斷(Preemptive Suspension):在GC發生時,主動中斷全部線程,不須要線程執行的代碼主動配合。幾乎不被採用。
  • 主動式中斷(Voluntary Suspension):設一個標誌,各個線程主動去輪詢這個標誌,遇到中斷則暫停。輪詢地方與安全點重合。 線程

安全區域(Safe Region)

指在一段代碼片斷中,引用關係不會發生變化。在這個區域的任意地方開始GC都是安全的。 對象

線程執行到安全區域中的代碼時,首先標識本身進入了安全區域;在離開安全區域時,要檢查系統是否已經完成了枚舉根節點(或整個GC過程),完成了就繼續執行,不然必須等待直到收到能夠安全離開Safe Region的信號。 排序

安全區域是爲了解決線程Sleep或Blocked狀態的。 隊列

垃圾收集器

前面的垃圾收集算法是理論,垃圾收集器則是具體的實現。

下圖是HotSpot裏的收集器,中間的橫線表示分代,有連線表示能夠組合使用。

Serial 收集器

是一個單線程的收集器,只能使用一個CPU或一條線程去完成垃圾收集;在進行垃圾收集時,必須暫停全部其餘工做線程,直到收集完成。

Serial/Serial Old 收集器運行示意圖:
Serial_gc_runtime

缺點:Stop-The-World。

優點:簡單。對於但CPU的狀況,因爲沒有多線程交互開銷,反而能夠更高效。

是Client模式下默認的新生代收集器。

ParNew 收集器

是Serial收集器的多線程版本。

ParNew/Serial Old 收集器運行示意圖:
ParNew_gc_runtime

垃圾收集語境下的併發與並行概念

  • 並行(Parallel):指多條垃圾收集線程並行工做,用戶線程仍然處於等待狀態。
  • 併發(Concurrent):用戶線程與垃圾收集線程同時執行。

Parallel Scavenge 收集器

新生代收集器,使用複製算法、並行的多線程收集器。

Parallel Scavenge 收集器的目標是達到一個可控制的吞吐量(Throughput)。這裏的吞吐量是指CPU用於運行用戶代碼的時間與CPU總消耗時間的比值。主要適合在後臺運算而不須要太多交互的任務。

Parallel Scavenge 收集器容許採用GC自適應的調節策略,也就是讓虛擬機根據收集到的運行時數據自行決定各個分代的大小等與垃圾回收有關的配置。

Serial Old 收集器

用於老年代的Serial收集器,單線程,使用「標記-整理」算法。

SerialOld_gc_runtime

主要在Client模式下使用。

Parallel Old 收集器

Parallel Scavenge的老年代版本,多線程,使用「標記-整理」算法。

ParallelOld_gc_runtime

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。基於「標記-清除」算法。

CMS_gc_runtime

運做過程分爲4個階段:

  • 初始標記(CMS initial mark):值標記GC Roots能直接關聯到的對象。
  • 併發標記(CMS concurrent mark):進行GC RootsTracing的過程。
  • 從新標記(CMS remark):修正併發標記期間因用戶程序繼續運行而致使標記發生改變的那一部分對象的標記。
  • 併發清除(CMS concurrent sweep):

其中標記和從新標記兩個階段仍然須要Stop-The-World,整個過程當中耗時最長的併發標記和併發清除過程當中收集器均可以和用戶線程一塊兒工做。

CMS收集器對CPU資源很是敏感。

浮動垃圾(Floating Garbage):因爲CMS併發清理階段用戶線程還在運行着,天然會有新的垃圾產生,而這些垃圾是在標記過程以後,CMS只能在下次GC時回收它們,這些垃圾就稱爲浮動垃圾。

CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure」失敗而致使另外一次Full GC的產生。

在垃圾收集階段用戶線程還在運行,所以須要預留足夠的內存給用戶線程使用。若是預留內存不能知足用戶線程,會出現「Concurrent Mode Failure」,這時虛擬機將啓動臨時後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集。

因爲CMS使用的是清除算法,會致使內存碎片問題,所以提供了參數用於控制是否在進行FullGC後進行內存整理,還提供了參數用於控制在多少次FullGC時才進行內存整理。內存整理是不能併發的,也就是要暫停全部用戶線程。

G1 收集器

G1(Garbage First):是一款面向服務端應用的垃圾收集器,用於替換CMS收集器。

G1_gc_runtime

G1將整個Java堆劃分爲大小相等的獨立區域(Region);新生代和老年代再也不是物理隔離的,都由一組不連續的Region組成。

G1的特色:

  • 並行與併發:充分利用多CPU縮短Stop-The-World停頓時間,在收集過程當中用併發的方式讓Java線程繼續執行。
  • 分代收集:仍然有分代的概念,不須要其餘收集器配合,獨立管理整個GC堆。
  • 空間整合:從總體看,是基於「標記-整理」算法實現的,從局部(兩個Region之間)看是基於「複製」算法的。在運行期間不會產生內存碎片。
  • 可預測的停頓:G1跟蹤各個Region裏垃圾堆積值的價值大小,維護一個優先級隊列,每次根據容許的時間,優先回收價值最大的Region。(這也是Garbage First的由來)

Region之間的對象引用以及其餘收集器中的新生代與老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描的。

G1中每一個Region都有一個與之對應的Remembered Set,虛擬機發現程序對引用類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查引用的對象是否處於不一樣的Region中(在其餘收集器中就是檢查是否老年代中的對象引用了新生代中對象),若是是, 經過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏。

G1垃圾回收主要有4個階段:

  • 初始標記:只標記GC Roots能直接關聯到的對象,而且修改TAMS(Next Top at Mark Start)值,讓下一階段用戶程序併發運行時,能在正確可用的Region中建立新對象。此階段須要暫停用戶線程。
  • 併發標記:從GC Roots開始對堆中對象進行可達性分析,找出存活對象;耗時較長,可與用戶線程併發執行。
  • 最終標記:修正在併發標記期間有變更的標記記錄。
  • 刷選回收:對各個Region的回收價值和成本進行排序,根據用戶指望的GC停頓時間制定回收計劃,進行垃圾回收。

內存分配與回收規則

內存分配與回收規則由垃圾回收器和內存有關參數決定,不是固定的。

兩個概念:

  • MinorGC,次收集:在新生代發生的垃圾收集,速度快,發生頻繁。
  • FullGC,MajorGC,主收集:發生在年老代的垃圾收集。通常伴隨一次MinorGC。

通常的規則:

  • 對象優先在Eden分配。當Eden沒有足夠的空間時,虛擬機將發起一次MinorGC。

  • 大對象直接進入老年代。大對象是指須要連續內存空間的Java對象。目的是避免在Eden區及兩個Survivor區之間大量的內存複製(新生代採用複製算法收集內存)。

  • 長期存活對象將進入老年代。虛擬機給每一個對象定義一個對象年齡計數器,若是對象在Eden出生並通過一次 Minor GC後仍然存活,而且可以被Survivor容納,將被移動到Survivor空間,而且對象年齡設爲1。對象在Survivor區中每「熬過」一次 MinorGC,年齡就加1,達到某個閥值就晉升到年老代。

  • 空間分配擔保。在發生Minor GC以前,虛擬機會先檢查年老代最大可用的連續空間算法大於新生代全部對象總空間,若是是,那麼Minor GC能夠確保是安全的。若是否,虛擬機會查看HandlePromotionFailure設置是否容許擔保失敗。若是容許繼續檢查老年代最大可用的連續 空間是否大於歷次晉升到老年代對象的平均大小,若是大於,將嘗試進行一次Minor GC,這是有風險的(存活對象佔用的內存大於平均大小,將致使HandlePromotionFailure失敗,從新發起一次Full GC);若是小於或者HandlePromotionFailure設置不容許冒險,將改成Full GC。

相關文章
相關標籤/搜索