淺析堆與垃圾回收
這篇文章我們主要關注這些問題::Java程式執行完後,堆中的對象什麼時候被回收?如何回收?
堆又叫做 「GC堆,”由於現在收集器基本都採用分代收集演算法,所以Java堆還可以細分為:新生代和老年代,比例是1:2;再細緻一點新生代內部又劃分為Eden區、Survivor區,比例為8:1。下圖顯示了堆的結構:
對象在堆中記憶體的分配是有嚴格規定的,策略為:
- 對象優先在新生代Eden區分配記憶體;
- 大對象直接進老年代,主要是長字元串和數組這些需要大量連續記憶體空間的對象;
- 長期存活的對象進入老年代。Eden區記憶體不夠時,JVM發起一次MinorGC,對象的年齡加一,默認對象年齡到15時進入老年代;
- 動態年齡判定。相同年齡所有對象大小的總和大於 Survivor 空間的一半,大於等於該年齡的對象進入老年代。
- 老年代空間不足;
- 方法區空間不足;
- 調用System.gc(),建議JVM進行full gc;
- 長期存活的對象轉入老年代,空間不足;
- 沒有足夠的連續空間分配給大對象;
- 新生代垃圾回收存活的對象太多,S1放不下,老年代擔保空間不足,擔保空間指的是老年代最大可用的連續空間是否大於新生代所有對象總空間。
堆裡面幾乎放了所有的對象,那我們怎麼知道這些對象是否還有用呢?JVM提供了兩種方法來判定:
- 強引用,new出來的對象,垃圾回收器絕不會回收它;
- 軟引用,在系統將要發生OMM前會回收這些對象的記憶體;
- 弱引用,垃圾收集器工作時只要發現,馬上回收;
- 虛引用,形同虛設,任何時候都可能被回收。
我們已經知道對象什麼時候被回收了,那如何回收呢?介紹四種最常用的垃圾回收演算法:
垃圾收集演算法是一種記憶體回收的思想,具體的實現是垃圾收集器。簡要介紹下常用的垃圾收集器:
- serial串列收集器。單執行緒,垃圾回收的時候,必須暫停其他工作。新生複製,老年標記整理。簡單高效;
- ParNew 收集器。serial的多執行緒版本;
- Parallel Scavenge 收集器,複製演算法的多執行緒收集器。注重吞吐量,cpu運行程式碼時間/cpu耗時總時間。新生複製,老年標記整理;
- Serial Old 收集器,老年代版本;
- Parallel Old 收集器,Parallel Scavenge老年代版本;
- CMS 收集器,注重最短時間停頓。並發收集器,垃圾收集執行緒與用戶執行緒(基本上)同時工作。 標記清除演算法
關於垃圾收集器更多的細節可以閱讀周志朋老師的書。
參考資料:《深入理解Java虛擬機》第二版 周志朋
《深入拆解Java虛擬機》鄭雨迪
《JVM虛擬機底層原理分析與性能調優》程式設計師諸葛