1. 程式人生 > >JVM垃圾收集(Garbage Collection,GC)

JVM垃圾收集(Garbage Collection,GC)

引言

JVM垃圾回收是一項自動化的過程,用來管理程式執行時所使用的記憶體

1. GC(Garbage Collection)概念

GC名稱 資訊描述
新生代GC(Minor GC) 發生在新生代(Eden、From Survivor、To Survivor)的垃圾收集動作
老年代GC(Major GC) 發生在老年代的GC
FullGC 清理整個堆空間——包括年輕代和老年代

2. 物件存活判定演算法

在堆裡存放著物件例項,Java虛擬機器(HotSpot)的GC收集器在對堆進行回收前,需要先判斷物件是否存活

演算法名稱 資訊描述
引用計數演算法 - 給物件新增一個引用計數器,每當有一個地方引用他時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件是不可能再被使用的 - Java虛擬機器裡面沒有選用引用計數演算法來管理記憶體
。主要原因是它很難解決物件之間相互迴圈引用的問題
可達性分析演算法 - 通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,證明此物件是不可用的 - Java虛擬機器所採用的演算法

Java中,可作為GC Roots的物件包括以下幾種:

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 本地方法棧中JNI(Java Native Interface,即Native方法)引用的物件

生存還是死亡

  • 要真正宣告一個物件死亡,至少要經過兩次標記過程

3. GC演算法

標記-清除(Mark-Sweep)演算法

分為“標記”和“清除”兩個階段:先標記所有需要回收的物件,在標記完成後統一回收所有被標記的物件

複製(Copying)演算法

  • 將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就講還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉
  • 新生代中的物件98%是朝生夕死的,所以並不需要按照1:1的比例來劃分記憶體空間,而是將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor
  • HotSpot虛擬機器預設Eden和1個Survivor的大小比例是8:1,也就是每次新生代中可用記憶體空間為整個新生代容量的90% HotSpot虛擬機器堆記憶體分配

標記-整理(Mark-Compact)演算法

標記過程與“標記-清除”演算法一樣,後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動

分代收集(Generational Collection)演算法

根據各個年代的特點採用最適當的收集演算法,新生代選用複製演算法老年代選用“標記-清理”或者“標記-整理”演算法

4. HotSpot的演算法實現

列舉根節點

  • GC進行時必須停頓所有Java執行執行緒(Stop The World),確保不會出現在分析期間引用關係還在不斷變化的情況
  • 使用OopMap資料結構快速且準確地完成GC Roots列舉

安全點(Safepoint)

  • GC停頓的位置
  • 搶先式中斷
  • 主動式中斷

安全區域(Safe Region)

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

5. GC收集器

GC收集器 特性
Serial - 新生代收集器- 複製演算法- 單執行緒收集器- 虛擬機器執行在Client模式下的預設新生代收集器
ParNew - 新生代收集器- 複製演算法- Serial收集器的多執行緒版本- 是許多執行在Server模式下的虛擬機器中首選的新生代收集器- 除了Serial收集器外,目前只有ParNew能與CMS收集器配合工作
Parallel Scavenge - 新生代收集器- 複製演算法- 並行的多執行緒收集器- 目標:達到一個可控制的吞吐量- 吞吐量:CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)- GC自適應調節策略
Serial Old - 老年代收集器- 單執行緒收集器- 標記-整理演算法- Client模式下的虛擬機器使用
Parallel Old
CMS(Concurrent Mark Sweep) - 一種以獲取最短回收停頓時間為目標的收集器- 老年代收集器- 標記-清除演算法- 真正意義上的併發收集器,第一次實現了讓垃圾收集執行緒與與使用者執行緒(基本上)同時工作- 初始標記- 併發標記- 重新標記- 併發清除
G1 - 面向服務端的垃圾收集器- 當今收集器技術發展的最前沿成果之一- 使用G1收集器時,Java堆的記憶體佈局就與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,都是一部分Region的集合- G1跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region- 初始標記- 併發標記- 最終標記- 篩選回收

6. 記憶體分配與回收策略

物件的記憶體分配,就是在堆上分配(但也可能經過JIT編譯後被拆散為標量型別並間接地棧上分配),物件主要分配在新生代的Eden區上,如果啟動了本地執行緒分配緩衝,將按執行緒優先在TLAB上分配。少數情況下也可能會直接分配在老年代中,分配的規則不是百分百固定的,其細節取決於當前使用的是哪一種垃圾收集器組合,還有虛擬機器中與記憶體相關的引數的設定

物件優先在 Eden 分配

  • 大多數情況下,物件在新生代Eden區中分配
  • 當Eden區沒有足夠的空間進行分配時,虛擬機器將發起一次Minor GC

大物件直接進入老年代

  • 大物件指需要大量連續記憶體空間的Java物件。典型的大物件就是很長的字串以及陣列

長期存活的物件將進入老年代

  • 虛擬機器給每個物件定義了一個物件年齡計數器
  • 如果物件在Eden出生並經過第一次Minor
  • GC後任然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設為1
  • 物件在Survivor區中每“熬過”一次Minor GC,年齡就增加1歲,當年齡增加到一定程度(預設為15歲),就將會晉升到老年代中
  • 物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold設定

動態物件年齡判定

  • 為了能夠更好的適應不同程式的記憶體狀況,虛擬機器並不是永遠的要求物件的年齡必須達到了MaxTenuringThreshold才能晉升老年代
  • 如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡

空間分配擔保

  • 在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的
  • JDK 6 Update 24之後的規則變為只要老年代的連續空間大於新生代物件總大小

參考資料

《深入理解Java虛擬機器:JVM高階特性與最佳實踐》第2版 周志明 著

說明

JVM相關文章為讀書筆記,內容來源於書本,僅供學習記錄