1 什麼時候回收垃圾?
1、什麼場景下該使用什麼垃圾回收策略?
在對記憶體要求苛刻的場景:想辦法提高物件的回收效率,多回收掉一些物件,騰出更多記憶體。
在CPU使用率高的情況下:降低高併發時垃圾回收的頻率,讓CPU更多的去執行你的業務而不是垃圾回收。
2、垃圾回收發生在哪些區域?
堆(回收物件)、方法區(不用的常量、類)
3、物件什麼時候被回收?
引用計數法:通過物件的引用計數器來判斷物件是否被引用。無法處理迴圈引用問題。
A --> B --> C --> D --> B 此時B被引用了兩次
當A不再引用B
A B --> C --> D --> B BCD迴圈引用無法被回收。
可達性分析法:以根物件(GC Roots)為起點向下搜尋,形成引用鏈。如果物件不在引用鏈上,便視作物件不可達,可以回收。
哪些物件可以作為GC Roots物件?
- 虛擬機器棧(棧幀中的本地變數表)中引用的物件
- 方法區中類靜態屬性引用的物件
- 方法區中常量引用的物件
- 本地方法棧中JNI(即 Native方法)引用的物件
注意的是,一個物件不可達,也不一定會被回收。
public class GCTest {
private static GCTest obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize方法被呼叫了");
obj = this; // 重新引用物件,從FQ佇列中出隊
}
public static void main(String[] args) throws InterruptedException {
obj = new GCTest();
obj = null;
System.gc();
TimeUnit.MILLISECONDS.sleep(1000);
extracted();
TimeUnit.MILLISECONDS.sleep(1000);
obj = null; // finalize()方法已被呼叫過,物件會被直接回收
System.gc();
extracted();
}
private static void extracted() {
if (obj == null) {
System.out.println("obj == null");
} else {
System.out.println("obj可用");
}
}
}
finalize方法被呼叫了
obj可用
obj == null
finalize()的建議
-
避免使用finalize()方法,操作不當可能會導致問題。上面的例子中,第二次回收時如果不設定obj==null,物件很難被回收掉。
-
finalize()優先順序低,何時會被呼叫無法確定,因為什麼時間發生GC不確定。
-
建議使用try...catch...finally來替代finalize()。
4、四種引用
強引用,例如new物件,永遠不會回收物件。
軟引用,描述有用但是非必需物件,只有記憶體不足時被回收。
弱引用,描述非必需物件,無論記憶體情況都會被回收。
虛引用,主要用來跟蹤物件被垃圾回收器回收的活動,結合引用佇列使用。不影響物件的生命週期。
Object obj = new Object();
SoftReference<String> sr = new SoftReference<>("helllo");
WeakReference<String> sr = new WeakReference<>("helllo");
ReferenceQueue< String> queue = new ReferenceQueue<>();
PhantomReference< String> pr= new PhantomReference<>(“hello“queue)
2 垃圾回收演算法
垃圾回收演算法
基礎垃圾回收演算法
-
標記清除法(Mark-Sweep):標記後直接刪除。
- 容易產生記憶體碎片;分配大記憶體時速度受到影響,需要找到適合它大小的空間。
-
標記整理法(Mark-Compact):標記,記憶體整理到一側,刪除三步。
- 整理存在開銷,沒有碎片。
-
複製(Copy):記憶體分為兩塊,每次只使用其中一塊;將存活物件複製到另一塊記憶體,然後清空當前記憶體;交換使用另一塊記憶體。
- 記憶體利用率低,效能好,沒有碎片。
綜合垃圾回收演算法
- 分代回收演算法:把記憶體分為多個區域,不同區域使用不同的回收演算法。
- 增量演算法:每次只收集一小塊記憶體區域的垃圾。
分代收集演算法
商業虛擬機器普遍採用分代收集演算法,根據物件的存活週期,把記憶體分為多個區域,不同區域使用不同的回收演算法。
分代的好處是更有效的清除不再需要的物件,提升垃圾回收的效率。
- 新生代回收(Minor GC | Young GC)
- 老年代回收(Major GC),通常伴隨Full GC,Major GC≈ Full GC
- 清理整個堆(Full GC)
典型的物件分配流程如下。
新生代採用的是複製演算法,存放存活週期短的物件。老年代物件存活時間長,採用標記-清除或標記-整理演算法。
堆的年輕代和老年代,記憶體比例1:2。年輕代分為Eden和Survivor,記憶體比例8:1:1。
1. 物件首先存放到Eden中,當Eden記憶體滿後,垃圾收集執行緒執行minor gc,通過GC Root進行可達性分析,找到非垃圾物件,複製到Survivor S0區(物件頭中分代年齡標記為1),清空Eden區域,Eden又可以儲存新物件。
2. 當Eden再次滿後,同時對Eden和S0執行minor gc,將非垃圾物件複製到S1(分代年齡加1),清空Eden和S0。S0和S1,同一時間只有一個被使用。
3. 當分代年齡達到15後,物件會被複制到老年代。
4. 當老年代滿了,會對年輕代和老年代執行full GC。full GC不起作用,則會OOM。
命令列輸入jvisualvm
,開啟Java VisualVM
工具的Visual GC外掛
可以看到程式執行過程中堆容量變化。每次Eden滿進行GC,調整堆區域。
非典型的情況:
- 新建的物件不一定分配到伊甸園。
-
物件大於
-XX:PretenureSizeThreshold
,就會直接分配到老年代 -
新生代空間不夠。例如大陣列。
- 物件也不一定要達到年齡才進入老年代
動態年齡:如果Survivor空間中所有相同年齡物件大小的總和大於Survivor空間的一半,那麼年齡大於等於該年齡的物件就可以直接進入老年代。
觸發垃圾回收的條件
Minor GC觸發條件:伊甸園空間不足
Full GC觸發條件:
- 老年代空間不足(老年代已滿;空間碎片太多,沒有連續記憶體分配物件)
- 元空間不足
- 要轉成老年代物件所需的空間大於老年代剩餘空間。
- 顯示呼叫System.gc(),建議垃圾回收器執行垃圾回收。設定
-XX:+DisableExplicitGC
引數忽略該操作。
分代收集調優思路
- 讓GC儘量發生在新生代,儘量減少Full GC的發生。
- 合理設定Survior區域的大小,避免記憶體浪費。(複製演算法)