垃圾回收 筆記記錄
- 1. 如何判斷對象可以回收
- 1.1 引用計數法
- 1.1.1 缺點
- 1.2 可達性分析算法
- 1.2.1 可達分析、根對象
- 1.2.2 優缺點
- 1.3 四種引用(強軟弱虛)
- 1.3.1 軟引用的實際使用案例
- 1.3.2 軟引用-引用隊列
- 1.3.3 弱引用的實際使用案例
- 2. 垃圾回收算法
- 2.1 標記清除算法
- 2.2 標記整理
- 2.3 復制
- 3. 分代垃圾回收
- 4. 垃圾回收器
- 4.1 吞吐量優先
- 4.2 響應時間優先
- 5. 垃圾回收調優
1. 如何判斷對象可以回收
下面介紹一些判斷對象是否可以被回收的算法。
1.1 引用計數法
基本原理
- 每個對象關聯一個計數器(整數),記錄當前有多少引用指向它。
- 引用增加時(如被變量賦值、被其他對象引用),計數器+1。
- 引用減少時(如變量離開作用域、被顯式置為null),計數器-1。
- 當計數器歸零,說明對象不再被任何引用指向,立即回收其內存。
1.1.1 缺點
引用計數法雖然簡單,但存在一個致命問題:無法解決循環引用。例如:
class A {B b;
}class B {A a;
}public class Main {public static void main(String[] args) {A a = new A(); // A 的引用計數 = 1B b = new B(); // B 的引用計數 = 1a.b = b; // B 的引用計數 = 2b.a = a; // A 的引用計數 = 2a = null; // A 的引用計數 = 1b = null; // B 的引用計數 = 1// 此時 A 和 B 互相引用,引用計數都不為 0,但已經無法訪問,造成內存泄漏!}
}
問題:
- 即使 a 和 b 已經不再被外部引用,但由于它們互相引用,引用計數仍然 > 0,導致無法回收,造成內存泄漏。
- Java 的解決方案:采用可達性分析(Reachability Analysis),從 GC Roots(如靜態變量、活動線程棧變量等)出發,標記所有可達對象,未被標記的則回收。
1.2 可達性分析算法
- 可達性分析算法(Reachability Analysis)是 Java 垃圾回收(GC)的核心算法,用于判斷對象是否存活。相比引用計數法,它能有效解決循環引用問題,是現代 JVM 采用的默認策略。
- 從一組「GC Roots」出發,遍歷所有能被引用的對象,未被遍歷到的即為垃圾。類似于“從樹根出發,標記所有可達的樹枝和樹葉,剩下的就是需要清理的枯枝”。
- 肯定不能當成垃圾被回收的對象稱為根對象
1.2.1 可達分析、根對象
- Java虛擬機中的垃圾回收器采用可達性分析來探索所有存活的對象。
- 掃描堆中的對象,看是否能夠沿著GC Root對象為起點的引用鏈找到該對象,找不到,表示可以回收。
- 哪些對象可以作為GC Root? 這里可以使用eclipse提供的MAT來找到
MAT中有一個功能就是找到當前快照中的GC Roots
GC Roots 構成:
jps 找到當前運行類的進程id
再配合jmap -dump:format=b,live,file=1.bin 21384
dump就是把堆內存當前運行的狀態轉儲成一個文件。
format表示轉儲文件的格式,b是二進制格式。
live會主動觸發一次垃圾回收,我們關注一次回收后還存活的對象。
file=1.bin 表示存儲的文件名稱。
21384就是jps看到的進程id.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 通過Eclipse的MAT 查看哪些是GC Roots對象* */
public class Demo {public static void main(String[] args) throws IOException {List<Object> list=new ArrayList<>();list.add("a");list.add("b");System.out.println(1);System.in.read();list=null;System.out.println(2);System.in.read();System.out.println("end...");}
}
1.2.2 優缺點
1.3 四種引用(強軟弱虛)
- 強引用:默認的引用類型,只要強引用存在,對象就不會被 GC 回收。即使內存不足(OOM),JVM 也不會回收強引用對象,而是拋出 OutOfMemoryError。 通對象創建(如 String s = “hello”),我們平時寫代碼new xx()都屬于強引用。
- 軟引用:當 內存不足時,GC 會回收軟引用對象。適合實現 內存敏感的緩存(如圖片緩存)。
- 弱引用:只要發生 GC,無論內存是否充足,弱引用對象都會被回收。比軟引用更短暫,適合存儲 非必須的元數據。
- 虛引用:無法通過虛引用獲取對象(get() 始終返回 null)。
唯一作用:對象被回收時收到系統通知(通過 ReferenceQueue)。
必須與 ReferenceQueue 聯合使用。- 其實還有一種,終結器引用:是一種與對象生命周期相關的特殊機制,它通過 finalize() 方法在對象被垃圾回收(GC)前提供一次“臨終拯救”機會。但因其設計缺陷,Java 9 開始已被標記為廢棄(@Deprecated),并建議使用更安全的替代方案(如 Cleaner)。
四種引用的對比:
1.3.1 軟引用的實際使用案例
這里設置堆內存是20m Xmx20m,對于某些圖片資源非核心業務,如果用強引用進行引用就會有可能導致內存溢出。這種不太重要的資源可以在內存緊張時將內存釋放掉,這種場景就可以使用軟引用。
下面代碼演示放入20m的對象,發現堆內存不夠直接OOM了,后面的例子使用軟引用就不會OOM。
private static final int _4MB=4*1024*1024;public static void main(String[] args) throws IOException {List<byte []> list=new ArrayList<>();for (int i=0;i<5;i++){list.add(new byte[_4MB]);}System.in.read();}
使用軟引用:內存不足時被回收
前4個都被釋放了,只有最后1個byte數組還在。
public static void soft(){// list -->SoftReference --> byte[]List<SoftReference<byte[]>> list=new ArrayList<>();for (int i=0;i<5;i++){SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}System.out.println("循環結束:"+list.size());for (SoftReference<byte[]> softReference : list) {System.out.println(softReference.get());}}
1.3.2 軟引用-引用隊列
前面的演示我們發現,當使用軟引用的時候,可能前4個byte數組已經被釋放了,只保留了第5個在內存中。也就是最后遍歷list的時候很多元素都是null了,對這些軟引用對象,其實沒必要保留在list中了。這種情況我們希望把軟引用本身也做一個清理,既然釋放了,就把引用也清除掉。因為軟引用本身也要占用內存,盡管占用的相對較少。
如何清理無用的軟引用呢,就需要用到引用隊列。
public static void soft() {List<SoftReference<byte[]>> list = new ArrayList<>();//引用隊列ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 5; i++) {//關聯了引用隊列,當軟引用所關聯的byte[]被回收時,那么軟引用會被加入到引用隊列queue中去。SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}System.out.println("循環結束:" + list.size());//獲取隊列中的軟引用Reference<? extends byte[]> poll = queue.poll();while (poll != null) {list.remove(poll);poll = queue.poll();}System.out.println("list中剩余的:===================>");for (SoftReference<byte[]> softReference : list) {System.out.println(softReference.get());}}
這時候看到list只有未被回收的byte數組。
1.3.3 弱引用的實際使用案例
這里可以看到第10次可能是因為軟弱引用本身也比較多了,發生了fullgc,前面幾次沒快超出堆內存時也會發發生了gc,所以又幾個null值。
public static void weak() {List<WeakReference<byte[]>> list = new ArrayList<>();//引用隊列ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 10; i++) {WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB], queue);list.add(ref);for (WeakReference<byte[]> weakReference : list) {System.out.print(weakReference.get() + " ");}System.out.println();}}