史上最全GC原理
什麼是垃圾
定義
- 釋放已佔用的內存,防止內存泄露
- 清除已經死亡或者長時間未使用的對象內存
語言特性
-
c++手動回收垃圾
- 忘記回收
- 回收多次
-
java 自動回收
如何定位垃圾
引用計數法
- 對象頭中分配一片空間用於存儲對象引用次數
- 程序執行過程中完成,非STW
- 注意:Recycler 算法可解決循環引用,但在多線程環境下,引用計數變更也要進行昂貴的同步操作,性能較低,早期的編程語言會採用此算法
根可達性分析算法
-
GC Root
-
虛擬機棧中引用的對象
public static void testGC(){
StackLocalParameter s = new StackLocalParameter(“localParameter”);
s = null;
} -
方法區中類靜態屬性引用的變量
-
方法區中常量引用的對象
-
本地方法棧JNI中引用的對象
任何 native 接口都會使用某種本地方法棧,實現的本地方法接口是使用 C 連接模型的話,那麼它的本地方法棧就是 C 棧。當線程調用 Java 方法時,虛擬機會創建一個新的棧幀並壓入 Java 棧。然而當它調用的是本地方法時,虛擬機會保持 Java 棧不變,不再在線程的 Java 棧中壓入新的幀,虛擬機只是簡單地動態連接並直接調用指定的本地方法。
-
-
通過GC roots對象作爲起點開始向下搜索引用的對象,找到的對象都爲存活對象即可達,其他沒有標記的對象都爲垃圾
怎麼回收垃圾
有哪些回收方法
-
標記清除 mark sweep
- 1、從GC Root遍歷對象圖,標記出垃圾對象;2、再次遍歷清除
- 產生碎片,內存不連續,效率偏低(兩遍掃描)
-
複製copying
- 1、內存分爲兩塊;2、第一塊使用完成後將存活的對象複製到第二塊;3、清除第一塊內存;
- 沒有碎片,效率高,但浪費空間,大對象時複製成本較高
-
標記整理 mark compact
- 1、標記出所有存活對象;2、對存活對象按照整理順序(Compaction Order)整理到內存的一端;3、清理端以外的內存
- 沒有碎片、無浪費空間,但效率偏低(兩遍掃描,引用指針需要調整,內存變動頻繁)
-
分代算法Generational Collection
-
java堆空間
-
新生代1/3
-
Eden區8/10
- 98%的對象朝生夕死
-
From區1/10
-
To區1/10
-
-
老年代2/3
-
哪些對象會進入
-
大對象
- 需要大量連續內存空間的對象,避免在新生代產生大量複製
-
長期存活對象
-
對象頭中存放對象年齡,每經過一次minorgc年齡增加一次,默認到15時會進入
- 可配置:MaxTenuringThreshold
-
-
動態對象年齡
-
年齡1的佔用了33%,年齡2的佔用了33%,累加和超過默認的TargetSurvivorRatio(50%),年齡2和年齡3的對象都要晉升
- 有點負載均衡感覺
-
-
-
常用算法
-
-
-
是以上三種回收算法的組合算法
-
jvm中有哪些收集器
-
Serial old
-
分代收集器
-
ParNew
- 採用複製算法的多線程收集器
- 主要工作在 Young 區,可以通過 -XX:ParallelGCThreads 參數來控制收集的線程數,整個過程都是 STW 的,常與 CMS 組合使用。
-
CMS
-
目標:獲取最短回收停頓時間
-
算法:三色標記+標記清除算法+增量更新算法
-
步驟
- 1、初始標記:STW,標記GC Root直接引用的對象;
2、併發標記:從GC Root引用對象開始遍歷對象圖,標記出可達對象;;
3、重新標記:STW,採用增量更新算法重新標記2步因用戶線程增加引用的對象;
4、併發清理:清理未標記的垃圾對象;
5、併發重置:重置本次GC過程中的標記數據;
- 1、初始標記:STW,標記GC Root直接引用的對象;
-
問題
-
併發
-
搶佔用戶線程cpu資源
-
CMS默認回收線程數是(CPU個數+3)/4
這個公式的意思是當CPU大於4個時,保證回收線程佔用至少25%的CPU資源,這樣用戶線程佔用75%的CPU,這是可以接受的。
但是,如果CPU資源很少,比如只有兩個的時候怎麼辦?按照上面的公式,CMS會啓動1個GC線程。相當於GC線程佔用了50%的CPU資源,這就可能導致用戶程序的執行速度忽然降低了50%,50%已經是很明顯的降低了。
-
解決辦法:incremental mode(增量模式),執行過程中GC線程和用戶線程交替執行
-
-
浮動垃圾
-
併發清理過程中產生浮動垃圾,可以忽略,下次清理
-
解決辦法
-
提前回收機制:CMSInitiatingOccupancyFraction參數默認是內存佔用92%時啓動GC
- 如果設置99%,這是需要內存分配1%時會Concurrent Mode Failure錯誤,這是CMS默認啓動Serial Old收集器,效率更慢
-
動態檢查機制:UseCMSInitiatingOccupancyOnlyCMS參數設置CMS會根據歷史記錄,預測老年代還需要多久填滿及進行一次回收所需要的時間。在老年代空間用完之前,CMS可以根據自己的預測自動執行垃圾回收。
-
-
-
GC執行過程不確定
- 在併發標記或者清理階段會出現還沒有回收完成又一次觸發fullgc,這時會出現concurrent mode failure錯誤,此時會STW,用戶serioal old處理
-
-
標記清除算法
-
產生碎片化內存,分配效率慢
-
解決辦法
- UseCMSCompactAtFullCollection參數(默認開啓),在Full GC後開啓內存碎片整理,但是STW
- XX:CMSFullGCsBeforeCompaction,參數表示經歷多少次fullgc後對內存空間壓縮整理,默認爲0,每次fullgc會壓縮
-
-
-
-
最佳實踐配置
- -XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=80 //回收內存佔比
-XX:+UseCMSInitiatingOccupancyOnly //啓動動態檢查機制
-XX:CMSFullGCsBeforeCompaction=5//設置經歷多少次fc後開始壓縮整理碎片
- -XX:+UseConcMarkSweepGC
-
應用場景
- 多數應用於互聯網站或者 B/S 系統的服務器端上,JDK9 被標記棄用,JDK14 被刪除
-
-
-
分區收集器
-
G1
G1收集器在後臺維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來),比如一個Region花200ms能回收10M垃圾,另外一個Region花50ms能回收20M垃圾,在回收時間有限情況下,G1當然會優先選擇後面這個Region回收。==這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限時間內可以儘可能高的收集效率
-
目標:針對大內存、達到實時高效、高吞吐量
-
算法:三色標記+複製+標記壓縮+STAB
-
基本概念
-
分區region
-
物理分區,邏輯分代,內存區域分爲E O H S等,每個分代內存可以不連續
E代表是Eden區,S代表Survivor,O代表Old區,H代表humongous表示巨型對象(大小大小Region空間一半的對象)
-
單個分區取值1-32M,必須是2的冪次,-XX:G1HeapRegionSize
-
最多有2048個分區
-
-
SATB
-
Snapshot-At-The-Beginning,GC初始標記階段對堆內存活的對象做一次快照,作用是維持併發GC的正確性
-
如何保證正確性
-
Region中有兩個top-at-mark-start(TAMS)指針,分別爲prevTAMS(上一次標記的位置)和nextTAMS(下次標記的位置)。在TAMS以上的對象是新分配的,這是一種隱式的標記
- 解決了併發期間新對象分配
-
對象的引用被替換時,通過write barrier對引用字段複製進行環切AOP, 將舊引用記錄下來,所以效率會低些,然後在最終標記階段只掃描出有write barrier記錄的對象
- 解決了灰色對象到白色對象的引用斷開
-
-
問題:如果被替換的白對象就是要被收集的垃圾,這次的標記會讓它躲過GC,這就是float garbage,STAB精度偏低
-
-
寫屏障
這塊涉及到SATB標記算法的原理,SATB是指start at the beginning,即在併發收集週期的第一個階段(初始標記)是STW的,會給所有的分區做個快照,後面的掃描都是按照這個快照進行;在併發標記週期的第二個階段,併發標記,這是收集線程和應用線程同時進行的,這時候應用線程就可能修改了某些引用的值,導致上面那個快照不是完整的,因此G1就想了個辦法,我把在這個期間對對象引用的修改都記錄動作都記錄下來,有點像mysql的操作日誌。
-
RSet
-
Remember Set,每個分區中維護一個RSet,主要記錄其他分區引用本分區對象的關係,誰引用了我的對象
-
如何輔助GC
- YGC時,選定Y區的RSet作爲根集,裏面記錄old->young的跨帶引用,避免掃描整個old代區
- mixed gc時,old代中每個分區記錄old->old,young->old的RSet,不用掃描整個old分代區
-
-
-
CSet
- Collection Set,GC要收集的Region的集合(任意分代),跨分區的掃描RSet
-
停頓預測模型
- 通過模型統計計算出的歷史數據來預測本次回收需要選擇的Region數量,儘量滿足設置的目標
- 通過XX:MaxGCPauseMillis參數設置用戶期望的停頓時間,默認200ms
- 衰減標準偏差爲理論基礎
-
-
GC模式
-
Young GC
-
E區無法分配內存(達到閾值)時啓動,即E區和S區複製到Old區(MaxTenuringThreshold參數配置)或者另外一個S區,多線程並行執行
YoungGC的回收過程如下:
根掃描,跟CMS類似,Stop the world,掃描GC Roots對象。
處理Dirty card,更新RSet.
掃描RSet,掃描RSet中所有old區對掃描到的young區或者survivor去的引用。
拷貝掃描出的存活的對象到survivor2/old區
處理引用隊列,軟引用,弱引用,虛引用(下一篇優化中會再講一下這三種引用對gc的影響)
-
-
Mixed GC
-
只回收老年代部分region,一般發生在YGC之後,目的是複用YGC掃描的GC Root,減少stw
-
發生時機
- G1MixedGCLiveThresholdPercent參數控制老年代分區中的存活對象比例,達到閾值這個分區會放在RSet中,默認45
- G1HeapWastePercent參數控制,在一次younggc之後,可以允許的堆垃圾百佔比,超過這個值就會觸發mixedGC
-
步驟
-
1、初始標記:標記GC Roots,會STW,複用YoungGC的暫停時間,設置好所有分區的NTAMS值
-
2、根分區掃描(RootRegionScan)
- 和java程序並行執行,基於標記算法,對Survivor對象全部掃描標記爲gcroot
-
3、併發標記:從GC Root引用對象開始遍歷對象樹,標記出存活對象
-
4、最終標記:會STW,標記出在3階段發生變化的對象,同時處理STAB緩衝區;
-
5、清除:STW,清除標記的垃圾對象,清理之後,將存活對象複製到其他可用分區,主要解決內存碎片問題
-
1、對各個Region的回收價值和成本進行排序,根據用戶設置的停頓時間執行清除計劃
比如說老年代此時有1000個Region都滿了,但是因爲根據預期停頓時間,本次垃圾回收可能只能停頓200毫秒,那麼通過之前回收成本計算得知,可能回收其中800個Region剛好需要200ms,那麼就只會回收800個Region(Collection Set,要回收的集合),儘量把GC導致的停頓時間控制在我們指定的範圍內
-
2、採用複製算法,將一個region中的存活對象複製到另一個空的region,這樣好處在於不會產生碎片
-
-
-
-
-
使用場景
-
服務端垃圾收集器
-
多處理器,內存偏大,一般大於6G以上
-
需要低延遲的響應(停頓時間可控)
-
存在以下情況可以嘗試使用G1
- Full GC 次數太頻繁或者消耗時間太長
- 對象分配的頻率或代數提升(promotion)顯著變化
- 受夠了太長的垃圾回收或內存整理時間(超過0.5~1秒)
-
-
和CMS的區別
- 停頓時間可控
- 最終標記效率更高,G1只標記寫屏障記錄的對象,CMS Remark階段掃描所有對象,STW時間更長
- CMS清除階段是併發的,G1是STW
-
最佳實踐配置
-
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 //設置停頓時間,默認200
-XX:INitiatingHeapOccupancyPercent=45 //設置整個堆使用率超過設置的值時啓動Mix GC,默認45 -
不要設置年輕代的大小
通過-Xmn顯式設置年輕代的大小,會干擾G1收集器的默認行爲:
G1不再以設定的暫停時間爲目標,換句話說,如果設置了年輕代的大小,就無法實現自適應的調整來達到指定的暫停時間這個目標
G1不能按需擴大或縮小年輕代的大小 -
響應時間度量
不要根據平均響應時間(ART)來設置-XX:MaxGCPauseMillis=n這個參數,應該設置希望90%的GC都可以達到的暫停時間。這意味着90%的用戶請求不會超過這個響應時間,記住,這個值是一個目標,但是G1並不保證100%的GC暫停時間都可以達到這個目標
-
-XX:ParallelGCThreads=n //垃圾收集器的並行階段的垃圾收集線程數
-
-XX:ConcGCThreads=n //垃圾收集器併發執行GC的線程數
-
-
-
ZGC
-
Shenandoah
-
-
三色標記法
-
含義
- 黑:對象和屬性引用的對象已完成標記
- 灰:對象被標記,但屬性資源引用的對象沒有標記完成
- 白:對象沒有被標記,回收對象
-
問題
-
漏標(兩者缺一不可)
- Mutator將黑對象引用指向白對象
- Mutator刪除灰對象到白對象的直接或者間接引用
-
解決辦法
-
核心是解決其中一步即可
- CMS 增量更新+寫屏障
黑對象新增白對象引用時通過寫屏障記錄下來,在重新標記階段對記錄的從新標記,即黑色對象變爲灰色對象 - G1 Shenandoah STAB+寫屏障
灰色對象刪除了白色對象引用時,通過寫屏障記錄下來,然後重新標記階段再次標記 - ZGC 讀屏障(待學習和補充)
- CMS 增量更新+寫屏障
-
-
-
XMind - Trial Version