在 JVM 進行垃圾回收之前,我們需要先判斷一個對象是否存活,判斷對象是否存活採用了兩種方法: 引用計數法 給對象中添加一個引用計數器,每引用這個對象一次,計數器 +1,當引用失效時,計數器 -1。當引用計數器為 0 時,則表示該對象可被回收。 Java 不適用原因:無法解決對象互相迴圈引用的問題 ...
目錄
在 JVM 進行垃圾回收之前,我們需要先判斷一個對象是否存活,判斷對象是否存活採用了兩種方法:
引用計數法
給對象中添加一個引用計數器,每引用這個對象一次,計數器 +1,當引用失效時,計數器 -1。當引用計數器為 0 時,則表示該對象可被回收。
Java
不適用原因:無法解決對象互相迴圈引用的問題
可達性分析
以 GC Roots
為起點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈。若一個對象到 GC Roots
之間沒有任何引用鏈,則說明此對象是不可達的,應該被回收。
可以作為 GC Roots
對象的有:
- 虛擬機棧(棧幀中的局部變數表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中
JNI
(native 方法)引用的對象
在可達性分析過程中,對象引用類型會對對象的生命周期產生影響,Java
中有四種引用類型:
- 強引用:只要該引用還有效,那麼
GC
不會回收。 - 軟引用:只有當記憶體空間不足時,軟引用才會被回收,用
SoftReference
實現。 - 弱引用:弱引用關聯的對象只能存活到下一次
GC
收集前,用WeakReference
實現。 - 虛引用:虛引用與其他引用不同,虛引用不會決定對象的生命周期,我們無法通過虛引用獲取對象實例。虛引用的唯一目的是:當該對象被
GC
收集時,收到一個系統通知。用PhantomReference
來實現。
一個對象真正不可用需要經過兩次標記過程:
- 首先進行可達性分析,篩選出與
GC Roots
之間沒有任何引用鏈的對象,進行第一次標記。 - 第一次標記後還需要再進行一次篩選,篩選的條件為是否有必要執行
finalize()
方法。- 若對象沒有重寫
finalize()
方法,或者finalize()
方法已經被JVM
調用了,則沒有必要執行標記,GC
會回收該對象。 - 若有必要執行,那麼會先將對象放入
F-Queue
的隊列中,由JVM
開啟一個低優先順序的線程去執行(但不一定等待finalize()
方法執行完)
- 若對象沒有重寫
finalize()
是在對象記憶體被回收前會被再調用一次,如果對象在finalize()
方法中重新加入到引用鏈中,那麼就會將此對象移出要被回收的集合中。其他的對象則進行第二次標記,進行回收。
Java 中常見的垃圾回收演算法
標記-清除演算法
分為兩個階段:標記,清除
缺點:兩個階段的效率都不高,容易產生大量的記憶體碎片
複製演算法
將記憶體空間分為大小相同的兩塊空間,當一塊的記憶體使用完後,就將存活的對象複製到另一塊中,然後將之前使用的空間進行清除。
缺點:浪費了一半的記憶體
標記-整理演算法
標記整理先對記憶體中的對象進行標記,當標記完後讓所有存活的對象向一端移動,然後直接清除掉端邊界以外的記憶體。
分代回收演算法
將堆中的對象分為:新生代和老年代
- 新生代使用複製演算法
- 將新生代記憶體分為一塊大的
Eden
區和兩個小的Survivor
區域;每次垃圾回收都是掃描 Eden 區和From
區,將存活對象複製到To
區,然後交換From
區和To
區的名稱引用,下次垃圾回收是繼續將存活對象從From
區複製到To
區。當一個對象經過幾次新生代垃圾回收後依然存活,那麼就將對象複製到老年代區域中。 - 老年代可以使用標記-清除或者標記-整理演算法
在新生代中,每次收集都會有大量對象死去,所以我們選擇複製演算法,只需要付出少量對象的複製成本就可以完成每次的垃圾收集。老年代中對象的存活率是很高的,沒有額外的空間進行分配,所以使用標記-整理或者標記-清除演算法進行垃圾收集。
記憶體分配機制
- 對象優先在
Eden
區分配,當Eden
區沒有足夠的空間時就會發起一次Minor GC
- 大對象和長期存活的對象進入老年代
- 典型的大對象是很長的字元串和數組,可以通過設置
XX: PretenuringSizeThreshold
設置直接進入老年代的對象大小。 - 每個對象都有年齡計數器,每經過一次 GC,年齡計數器 +1,當達到一定程度時(預設為 15)就會進入老年代。可以通過修改
XX: MaxTenuringThreshold
來設置對象晉升老年代的閾值。
- 典型的大對象是很長的字元串和數組,可以通過設置
- 長期存活對象:每次垃圾回收都是掃描
Eden
區和From
區,將存活對象複製到To
區,然後交換From
區和To
區的名稱引用,下次垃圾回收是繼續將存活對象從From
區複製到To
區。當一個對象經過幾次新生代垃圾回收後依然存活,那麼就將對象複製到老年代區域中。
關於預設的晉升年齡是 15,這個說法的來源大部分都是《深入理解 Java 虛擬機》這本書。如果你去 Oracle 的官網閱讀相關的虛擬機參數,你會發現-XX: MaxTenuringThreshold=threshold 這裡有個說明**Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector. **預設晉升年齡並不都是 15,這個是要區分垃圾收集器的,CMS 就是 6。
Minor GC 和 Full GC
部分收集(Partial GC)
- 新生代 GC(Minor GC/ Young GC):指新生代 GC,Minor GC 收集非常頻繁,回收速度也比較快。
- 老年代 GC(Major GC/ Old GC):指老年代的 GC。在一些語境中 Major GC 也用於指整堆收集。
- 混合收集(Mixed GC):對整個新生代和部分老年代進行 GC。
整堆收集(Full GC):收集整個 Java 堆和方法區。
垃圾收集器
在新生代工作的垃圾收集器:Serial、ParNew、Parallel Scavenge
在老年代工作的垃圾收集器:CMS、Serial Old、Parallel Old
同時在新老年代工作的垃圾收集器:G1
- Serial 串列收集器
- 特性:單線程,
stop the world
,採用複製演算法 - 應用場景:
JVM
在Client
模式下預設的新生代收集器 - 優點:簡單高效
- 特性:單線程,
- ParNew
- 特點:是
Serial
的多線程版本,採用複製演算法 - 應用場景:在
Server
模式下常用的新生代收集器,可以和CMS
配合工作
- 特點:是
- Parallel Scavenge
- 特點:並行的多線程收集器,採用複製演算法,吞吐量優先,有自適應調節策略
- 應用場景:吞吐量大時使用
- SerialOld
- 特點:
Serial
的老年代版本,單線程,使用標記 - 整理演算法
- 特點:
- Parallel Old
Parallel Scavenge
的老年代版本,多線成,使用標記 - 整理演算法
- CMS
- 特點:以最短回收停頓時間為目標,使用標記 - 清除演算法
- 過程:
- 初始標記:
stop the world
標記GC Roots
能直接關聯到的對象 - 併發標記:進行
GC Roots Tracing
- 重新標記:
stop the world
;修正併發標記期間因用戶程式繼續運作而導致標記產生變動的 那一部分對象的標記記錄 - 併發清除:清除對象
- 初始標記:
- 優點:併發收集,低停頓
- 缺點:
- 對
CPU
資源敏感 - 無法處理浮動垃圾(併發清除 時,用戶線程仍在運行,此時產生的垃圾為浮動垃圾)
- 產生大量的空間碎片
- 對
- G1
- 特點:面向服務端應用,將整個堆劃分為大小相同的
region
。- 並行與併發
- 分代收集
- 空間整合:從整體看是基於 “標記 - 整理” 的,從局部(兩個
region
之間)看是基於 “複製” 的。 - 可預測的停頓:使用者可明確指定在一個長度為 M 毫秒的時間片段內,消耗在垃圾收集上的時間不得超過 N 毫秒。
- 執行過程:
- 初始標記:
stop the world
標記GC Roots
能直接關聯到的對象 - 併發標記:可達性分析
- 最終標記:修正在併發標記期間因用戶程式繼續運作而導致標記產生變動的那一部分標記記錄
- 篩選回收:篩選回收階段首先對各個
Region
的回收價值和成本進行排序,根據用戶所期望的GC
停頓時間來制定回收計劃
- 初始標記:
- 特點:面向服務端應用,將整個堆劃分為大小相同的
CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它需要消耗額外的 CPU 和記憶體資源,在 CPU 和記憶體資源緊張,CPU 較少時,會加重系統負擔。CMS 無法處理浮動垃圾。CMS 的 “標記 - 清除” 演算法,會導致大量空間碎片的產生。
G1 收集器,G1 (Garbage-First) 是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器. 以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征。
GC 自適應調節策略 Parallel Scavenge
收集器有一個參數 - XX:+UseAdaptiveSizePolicy
。當這個參數打開之後,就不需要手工指定新生代的大小、Eden
與 Survivor 區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱為 GC
自適應的調節策略(GC Ergonomics)。