這篇文章我們主要關注這些問題::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虛擬機器底層原理分析與效能調優》程式設計師諸葛