【證】:那些可作爲GC Roots的對象

一、名詞解釋

根搜索算法是JVM用來的判斷對象是否存活的算法,此算法基本思路爲通過一系列的“GC Roots”對象作爲起始點,從這些節點往下搜索,當一個對象和GC Roots不可達時,則該對象是無用的,可被回收的。如下圖所示:object5、object6、object7雖然互相有關聯,但是他們到GC Roots是不可達的,因此他們都可以被回收。

       

在java中,可作爲GC Roots的對象有:

1.虛擬機棧(棧幀中的本地變量表)中引用的對象;

2.方法區中的類靜態屬性引用的對象;

3.方法區中常量引用的對象;

4.本地方法棧中JNI(即一般說的Native方法)中引用的對象

二、驗證以上可作爲GC Roots的對象(此處只做最簡單的驗證,不涉及很複雜的GCRoots引用鏈)。

1.驗證虛擬機棧(棧幀中的局部變量)中引用的對象 作爲GC Roots

/**
 * GCRoots 測試:虛擬機棧(棧幀中的局部變量)中引用的對象作爲GCRoots 
 * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails
 * 
 * 擴展:虛擬機棧中存放了編譯器可知的八種基本數據類型,對象引用,returnAddress類型(指向了一條字節碼指令的地址)
 * @author ljl
 */
public class TestGCRoots01 {
	private int _10MB = 10 * 1024 * 1024;
	private byte[] memory = new byte[8 * _10MB];

	public static void main(String[] args) {
		method01();
		System.out.println("返回main方法");
		System.gc();
		System.out.println("第二次GC完成");
	}

	public static void method01() {
		TestGCRoots01 t = new TestGCRoots01();
		System.gc();
		System.out.println("第一次GC完成");
	}
}

控制檯打印日誌:

[GC [PSYoungGen: 105513K->616K(458752K)] 105513K->82536K(983040K), 0.0945986 secs] [Times: user=0.17 sys=0.06, real=0.09 secs] 
[Full GC [PSYoungGen: 616K->0K(458752K)] [ParOldGen: 81920K->82430K(524288K)] 82536K->82430K(983040K) [PSPermGen: 2547K->2546K(21504K)], 0.0273364 secs] [Times: user=0.06 sys=0.01, real=0.03 secs] 
第一次GC完成
返回main方法
[GC [PSYoungGen: 15728K->64K(458752K)] 98159K->82494K(983040K), 0.0014739 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 64K->0K(458752K)] [ParOldGen: 82430K->510K(524288K)] 82494K->510K(983040K) [PSPermGen: 2546K->2546K(21504K)], 0.0118484 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
第二次GC完成

第一次GC:

t爲局部變量,引用了new出的對象(80M),作爲GC Roots,在Minor GC後被轉移到老年代中,且Full GC也不會回收該對象,仍保留在老年代中。

第二次GC:

method01方法執行完後,局部變量t跟隨方法消失,不再有引用類型指向該對象,該對象在Full GC後,被完全回收,老年代騰出該對象之前所佔的空間。


2.驗證方法區中的靜態變量引用的對象作爲GC Roots

/**
 * 測試方法區中的靜態變量引用的對象作爲GCRoots
 * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails
 * 
 * 擴展:方法區存與堆一樣,是各個線程共享的內存區域,用於存放已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。
 * @author ljl
 * */
public class TestGCRoots02 {
	private static int _10MB = 10 * 1024 * 1024;
	private byte[] memory;

	private static TestGCRoots02 t;

	public TestGCRoots02(int size) {
		memory = new byte[size];
	}

	public static void main(String[] args) {
		TestGCRoots02 t2 = new TestGCRoots02(4 * _10MB);
		t2.t = new TestGCRoots02(8 * _10MB);
		t2 = null;
		System.gc();
	}
}


控制檯打印日誌:

[GC [PSYoungGen: 138608K->632K(458752K)] 138608K->82552K(983040K), 0.0684508 secs] [Times: user=0.19 sys=0.06, real=0.07 secs] 
[Full GC [PSYoungGen: 632K->0K(458752K)] [ParOldGen: 81920K->82427K(524288K)] 82552K->82427K(983040K) [PSPermGen: 2513K->2512K(21504K)], 0.0162803 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 

t2被置爲null,Minor GC後t2之前引用的對象(40M)被完全回收;t爲靜態變量,存放於方法區中,引用了對象(80M),在Minor GC後,被轉移到老年代中,且在Full GC後,也不會被回收,繼續保留在老年代中。


3.驗證方法區中常量引用對象作爲GC Roots

/**
 * 測試常量引用對象作爲GCRoots 
 * 注意:t修飾符如果只是final會被回收,static final不會被回收,所以static final 纔是常量的正確寫法
 * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails
 * @author ljl
 */
public class TestGCRoots03 {
	private static int _10MB = 10 * 1024 * 1024;
	private static final TestGCRoots03 t = new TestGCRoots03(8 * _10MB);
	private byte[] memory;

	public TestGCRoots03(int size) {
		memory = new byte[size];
	}

	public static void main(String[] args) {
		TestGCRoots03 t3 = new TestGCRoots03(4 * _10MB);
		t3 = null;
		System.gc();
	}
}

控制檯打印日誌:

[GC [PSYoungGen: 138608K->688K(458752K)] 138608K->82608K(983040K), 0.0514407 secs] [Times: user=0.13 sys=0.02, real=0.05 secs] 
[Full GC [PSYoungGen: 688K->0K(458752K)] [ParOldGen: 81920K->82428K(524288K)] 82608K->82428K(983040K) [PSPermGen: 2515K->2514K(21504K)], 0.0153884 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 

t3被置爲null,Minor GC後t3之前引用的對象(40M)被完全回收;t爲常量,存放於方法區中,引用了對象(80M),在Minor GC後,被轉移到老年代中,且在Full GC後,也不會被回收,繼續保留在老年代中。


4.測試成員變量是否可作爲GC Roots

/**
 * 測試成員變量引用對象是否可作爲GCRoots
 * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails
 *
 * @author ljl
 */
public class TestGCRoots04 {
	private static int _10MB = 10 * 1024 * 1024;
	private TestGCRoots04 t;
	private byte[] memory;

	public TestGCRoots04(int size) {
		memory = new byte[size];
	}

	public static void main(String[] args) {
		TestGCRoots04 t4 = new TestGCRoots04(4 * _10MB);
		t4.t = new TestGCRoots04(8 * _10MB);
		t4 = null;
		System.gc();
	}
}

控制檯打印日誌:

[GC [PSYoungGen: 138608K->600K(458752K)] 138608K->600K(983040K), 0.0015591 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 600K->0K(458752K)] [ParOldGen: 0K->507K(524288K)] 600K->507K(983040K) [PSPermGen: 2513K->2512K(21504K)], 0.0144441 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
t4被置爲null,Minor GC後t4之前引用的對象(40M)被完全回收;t爲成員變量,也叫實例變量,不同於類變量(靜態變量),前面講到類變量是存儲在方法區中,而成員變量是存儲在堆內存的對象中的,和對象共存亡,所以是不能作爲GC Roots的,從日誌中也可看出t在MinorGC後,跟隨t4一起被完全回收。不再佔用任何空間。


以上爲一個非常簡單的可作爲GC Roots的對象的驗證,不涉及較複雜的GC Roots引用鏈,其實作爲使用者來講,我們只要知道,哪些對象是可作爲GC Roots的,在實際開發過程中要特別注意這些對象,不要讓無謂的大對象消耗了資源,拖累了性能。



發佈了32 篇原創文章 · 獲贊 51 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章