對象是否存活 Java的GC基於可達性分析演算法(Python用引用計數法),通過可達性分析來判定對象是否存活。這個演算法的基本思想是通過一系列"GC Roots"的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時(圖論稱之為不可達), ...
Java的GC基於可達性分析演算法(Python用引用計數法),通過可達性分析來判定對象是否存活。這個演算法的基本思想是通過一系列"GC Roots"的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時(圖論稱之為不可達),則證明此對象是不可用的。
無論引用計數法,還是可達性分析都離不開“引用”的概念。Java將引用分為四種(強引用、軟引用,弱引用,虛引用),這四種引用強度依次逐漸減弱。
-
strong reference強引用,垃圾收集器永遠不會回收存在強引用的對象。(如Object obj = new object()就是一個強引用)
-
soft reference軟引用,描述一些還有用但是並非必須的對象。系統將要發生記憶體溢出異常之前,將會把這些軟引用對象列入回收範圍中進行二次回收。如果這次回收之後還是不夠記憶體,才會拋出記憶體溢出異常。
-
weak reference弱引用,也是用來描述非必須對象的,但是它的強度更弱。被弱引用的對象只能活到下一次GC發生之前。當GC發生,無論當前記憶體是否充足都會回收弱引用對象。
-
phantom reference虛引用(幽靈引用),是最弱的一種引用關係。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。
在可達性演算法中不可達的對象,不會直接死亡,如果它還沒有執行finalize方法,虛擬機將在一個優先順序很低的線程中觸發它的finalize方法(不保證完成),如果此時對象又將自己關聯到引用鏈上,那麼GC將把它移出“即將回收”的集合。
由於finalize方法只會被執行一次,所以這個“自救”過程也只會經歷一次。
垃圾回收方法
1、標記清除法
首先標記所有需要回收的對象,然後統一回收所有要回收的對象。方法簡單,但是效率不高,並且產生了很多記憶體碎片。
2、複製演算法
將記憶體分為大小相等的兩塊,每次只使用一塊。當一塊的記憶體用完了,就將還存活的對象複製到另一塊記憶體上,然後把使用過的那一半記憶體空間一次性清理掉。這樣再分配記憶體時只需要移動堆頂指針,實現簡單,運行高效。但是代價是實際可用記憶體縮小了一半,實在太高。
因為新生代大部分對象都是”朝生夕死“,所以虛擬機將記憶體分為1個Eden和2個Survivor空間,大小比例預設為8:1:1。當回收時,將Eden和Survivor中還存活著的對象複製到另一個Survivor空間上,然後清理掉Eden和剛纔用過的Survivor空間。這樣子就只需要犧牲10%的新生代記憶體。
3、標記-整理演算法
老年代對象存活率高,複製演算法在這時候效率很低,而且老年代沒有額外空間做分配擔保,所以不適用。根據這種特點,有人提出了標記-整理演算法。
首先標記所有要回收的對象,然後所有存活的對象都向一端移動,然後直接清理邊界以外的記憶體。
4、分代收集演算法
前面三個垃圾回收演算法都算是鋪墊,現在商業虛擬機的垃圾收集都採用”分代收集“。將Java堆分成新生代和老年代,新生代中每次GC有大量對象死去,只有少量存活,所以採用複製演算法;老年代中對象存活率高,沒有額外空間進行分配擔保,就採用標記-清除或者標記-整理演算法。
垃圾收集器
新生代:serial收集器
單線程,必須暫停其他所有工作線程(Stop The World),直到它收集結束。STW難以接受,但是簡單高效。
新生代:parNew收集器
serial的多線程版本。server模式下首選,因為它可以和cms配合使用。
新生代:parallel scavenge收集器
專註於達到可控制吞吐量的收集器,主要適合後臺運算而不用太多交互的任務。
老年代:serial old收集器
serial的老年代版本,單線程。使用標記-整理演算法。
老年代:parallel old收集器
parallel scavenge的老年代版本。使用標記-整理演算法。
老年代:cms收集器
經歷四個階段:
-
初始標記:需要STW,僅僅標記一下GC Roots能直接關聯的對象,所以很快。
-
併發標記:進行GC Roots Tracing
-
重新標記:需要STW,修正併發標記期間因程式運行導致標記產生變動的那一部分對象的標記記錄,時間比初始標記長,但是遠比併發標記短
-
併發清除:清除對象
由於耗時最長的併發標記和併發清除過程,收集器線程都可以和用戶線程一起工作,所以總體來說cms收集器的記憶體回收過程是和用戶線程一起併發執行的,停頓很短。
缺點:cpu資源敏感,特別是cpu核數較少時;無法處理浮動垃圾;cms基於標記-清除演算法,容易產生大量記憶體碎片。
G1收集器
最新的收集器,性能優秀,但是太複雜不再贅述。
記憶體分配和回收策略
-
對象優先在Eden分配,如果啟動了TLAB,將按照線程優先在TLAB上分配。
-
當Eden不夠空間時,虛擬機發起一次Minor GC(新生代GC)
-
大對象直接進入老年代
-
長期存活的對象將進入老年代(存活一次GC age+1,預設age 15時進入老年代)
-
Survivor空間中相同年齡所有年齡大小 的總和大於Survivor空間的一半 時,年齡大於等於該年齡的對象可以直接進入老年代
-
Minor GC時,虛擬機會檢查老年代的剩餘空間是否大於新生代對象總大小or