那些可作為GC Roots的對象


原文:https://blog.csdn.net/u010798968/article/details/72835255

 

一、名詞解釋

根搜索算法是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的,在實際開發過程中要特別注意這些對象,不要讓無謂的大對象消耗了資源,拖累了性能。

 

一、名詞解釋根搜索算法是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的,在實際開發過程中要特別注意這些對象,不要讓無謂的大對象消耗了資源,拖累了性能。


 ———————————————— 版權聲明:本文為CSDN博主「Etyero」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。原文鏈接:https://blog.csdn.net/u010798968/article/details/72835255


免責聲明!

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



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