GC算法與GC


JVM系列隨筆主要是對《深入理解Java虛擬機:JVM高級特性與最佳實踐 第2版》的學習總結

概述

GC(Garbage Collection)是垃圾收集的簡稱,比Java的歷史更加久遠。經過半個多世紀的發展,已經實現了自動化。作為學習,我們需要搞清楚GC的三件問題:

  1. 哪些內存需要回收?
  2. 什么時候回收?
  3. 如何回收?

前面的文字記錄了Java運行時區域,其中程序計數器,Java虛擬機棧和本地方法棧是線程隔離的,隨線程生而生,隨線程滅而滅。每一個棧幀中分配多少內存基本上是在類結構確定下來的時候就已知了,這幾個區域內存分配和回收具備確定性,方法或者線程結束時,自然就跟隨着回收了。而堆的和方法區不一樣,同一個接口的不同實現類需要的內存不同,只有程序運行期 才知道創建哪些對象,這部分內存的分配具有動態性,GC所關注的也是這部分區域。如下圖所示:

對象與引用

為了解決“哪些內存需要回收”的問題,需要確定哪些對象是“有用不可回收”的,而哪些對象是“無用可回收”的。通常存在以下兩種判斷算法。

引用計數法

算法原理:給對象添加一個引用計數器,每當一個地方引用它時,計數器值就加1;每當一個引用時效時,計數器值就減1;當引用計數為0時,表示該對象不再使用,可以回收。

應用:微軟COM/ActionScript3/Python

優勢:實現簡單,判定效率高,通常情況下是個不錯的算法。

不足:很難解決循環引用的問題。

可達性分析算法

算法原理:以稱作“GC Roots”的對象作為起點向下搜索,搜索所走過的路徑成為引用鏈,當一個對象到GC Roots不可達時,表示該對象不再使用,可以回收。

應用:Java/C#/Lisp

優勢:可以解決循環引用的問題

不足:算法略復雜

以下圖為例,無法通過已知的途徑獲取對象C和D,則有

  • 若使用引用計數算法判定,但是由於它們相互引用,導致引用計數不為0,因此無法回收掉。
  • 若使用可達性分析算法,C和D到GC Roots不可達,則可回收。

Java的4種引用類型

JDK1.2之后定義了4種引用,分別為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)虛引用(Phantom Reference),引用強度依次減弱。

  • 強引用

    我們經常使用的引用,形如Object o = new Object();或者String s = "Hello world!";等。只要強引用存在,被引用對象就不會被回收掉。

  • 軟引用

    描述一些還有用但是並非必須的對象。系統發生內存溢出之前,會把這類對象列入回收范圍,如果這次回收還沒有足夠內存,才拋出內存溢出異常。使用java.lang.ref.SoftReference類來表示。

  • 弱引用

    描述非必須對象。被引用對象只能存活到下一次GC之前。使用java.lang.ref.WeakReference類來表示。

  • 虛引用

  • 稱為幽靈引用或者幻影引用,是最弱的一種引用關系。一個對象的虛引用根本不影響其生存時間。為一個對象設置虛引用的唯一目的就是這個對象被GC時收到一條系統通知。使用java.lang.ref.PhantomReference類來表示。

關於四種引用的具體實例和應用場景,參考

對象回收流程

不可達對象並非立即被回收,還需要經過兩次標記過程后才被死亡:

  • 如果對象與GC Roots沒有連接的引用鏈,則它會被第一次標記。隨后進行一次是否執行finalize()方法的判定。
  • 如果有必要執行,則給對象被放置到一個叫做F-Queue的隊列中,稍后由虛擬機建立低優先級的Finalizer線程去執行,但並不承諾等待它運行結束。
  • 如果沒有必要執行(對象沒有覆蓋finalize()方法或者finalize()已經被執行過一次),則它會被第二次標記。

附上周總的例子:

/**
 * 代碼演示了兩點:
 * 1.對象在被GC時可以自我拯救
 * 2.這種自救的機會只有一次,因為一個對象的finalize()方法最多被系統執行1次
 */
public class FinalizeEscapeGC {
	public static FinalizeEscapeGC SAVE_HOOK = null;

	public void isAlive() {
		System.out.println("yes, i am still alive :)");
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("Finalize method Invoked!");
		FinalizeEscapeGC.SAVE_HOOK = this;
	}

	public static void main(String[] args) throws InterruptedException {
		SAVE_HOOK = new FinalizeEscapeGC();

		// 第一次拯救,成功
		SAVE_HOOK = null;
		System.gc();
		Thread.sleep(1000);

		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		} else {
			System.out.println("Oh, i am dead!  :(");
		}

		// 第二次拯救,失敗
		SAVE_HOOK = null;
		System.gc();
		Thread.sleep(1000);

		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		} else {
			System.out.println("Oh, i am dead!  :(");
		}
	}

}

輸入結果為:

Finalize method Invoked!
yes, i am still alive :)
Oh, i am dead!  :(

另外,finalize()方法是誕生時使C/C++程序員接受它所做的妥協,不建議使用。

方法區回收

方法區GC主要回收兩部分內容:廢棄常量和無用類。判斷是否為“廢棄常量”與堆中對象類似,而判斷一個類為“無用類”必須滿足下面三個條件:

  • 該類所有實例都被回收
  • 加載該類的ClassLoader已被回收
  • 該類對應的java.lang.Class對象沒有被引用

GC算法

HotSpot算法實現

GC分類

內存分配與回收策略


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM