JVM-04垃圾收集Garbage Collection(上)【垃圾对象的判定】
文章目錄
- 思維導圖
- 概述
- 如何判斷對象為垃圾對象
- 對象的存活還是死亡
- 判斷對象死亡的算法一:引用計數算法Reference Counting
- 原理
- 示意圖
- 優點
- 缺點
- 實驗
- 判斷對象死亡的算法一:可達性分析算法Reachability Analysis(hotspot采用該算法)
- 原理
- 示意圖
- 可作為GC Roots的對象
- 對象引用的分類
- 強引用
- 軟引用
- 弱引用
- 虛引用
- finalize 逃逸
- 原理
- 實驗
- 注意事項
- 回收方法區
- 廢棄常量的回收
- 無用的類的回收
思維導圖
概述
談起垃圾收集 (Garbage Collection ,GC),有3個問題是無法回避的
1. 哪些內存需要回收
2. 什么時候回收
3. 如何回收
這就引出了我們這邊博文需要討論的話題
1. 如何判斷對象為垃圾對象
2. 何時回收垃圾對象(垃圾收集算法)
3. 如何回收垃圾對象(垃圾收集器)
我們前面的博文中討論了Java的內存自動管理機制,我們知道java內存運行時區域可以分為兩大部分: 線程共享區域和線程獨占區域 。
線程共享區主要包括Java堆(存儲對象實例)和方法區(即我們常說的永久代【JDK7之后逐步去永久代,使用元數據區代替】)
線程獨占區主要包括:程序計數器、Java虛擬機棧、本地方法棧。 這3個區域以為是線程獨占區,因此生命周期同線程相同,隨線程而生而滅。 棧中的幀隨著方法的進入和退出有條不紊的執行著出棧和入棧操作。 每一個棧幀中分配多少內存基本上在類結構確定下來的時候就已知的,因此線程獨享區的內存分配和回收都具備確定性,這幾個區域就不需要過多考慮回收的問題,因為方法結束或者線程結束的時候,內存就跟著回收了。
而線程共享區(Java堆和方法區)則不一樣,一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也可能不一樣,我們只有在程序運行期間才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,垃圾收集器所關注的也是Java堆和方法區。我們這里討論的內存分配與回收也指的是這一部分.
如何判斷對象為垃圾對象
對象的存活還是死亡
Java堆內存中存放著幾乎所有的對象實例。
垃圾收集器在對堆內存進行回收之前,需要確定哪些對象是存活或者死去(即不可能再被任何途徑使用的對象)
判斷對象死亡的算法一:引用計數算法Reference Counting
原理
通過在對象頭中分配一個空間來保存該對象被引用的次數。如果該對象被其它對象引用,則它的引用計數加一,如果刪除對該對象的引用,那么它的引用計數就減一,當該對象的引用計數為0時,那么該對象就會被回收。
示意圖
優點
- 實現簡單
- 判斷效率高
缺點
- 無法解決對象之間相回循環引用的問題,易引起內存泄露
實驗
package com.artisan.gc;/*** * * @ClassName: ReferenceCountingGC* * @Description: VM Args* * @author: Mr.Yang* * @date: 2018年7月29日 上午10:31:32*/ public class ReferenceCountingGC {private Object instance;private static final int _1M = 1024 * 1024;// 設置個成員變量,在堆中占點內存,以便觀察GC是否回收相互引用的情況private byte[] bigByte = new byte[2 * _1M];public static void main(String[] args) {ReferenceCountingGC rc = new ReferenceCountingGC();ReferenceCountingGC rc2 = new ReferenceCountingGC();// 設置相互引用rc.instance = rc2;rc2.instance = rc;// 將對象置為空rc.instance = null;rc2.instance = null;// 垃圾回收,觀察rc 和 rc2能否被回收System.gc();}}虛擬機參數設置: -verbose:gc -XX:+PrintGCDetails
日志信息:
可以看到確實被回收了,這也側面驗證了我們現在使用的hotspot虛擬機不是采用該算法進行垃圾回收。
判斷對象死亡的算法一:可達性分析算法Reachability Analysis(hotspot采用該算法)
原理
通過一系列的稱為“GCsRoots”的對象作為起始點,從這些節點向下開始搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)。 當一個對象到GC Roots沒有任何引用鏈相連(即從GC Roots到這個對象不可達)時,則證明該對象是不可用的。
示意圖
可作為GC Roots的對象
1. 虛擬機棧(棧幀中的本地變量表)中引用的對象
2. 方法區中類靜態屬性引用的對象
3. 方法區中常量引用的對象
4. 本地方法棧中JNI(即native方法)引用的對象
對象引用的分類
JDK1.2之后,Java對引用的概念進行了擴充。
引用強度 強引用 Strong Reference > 軟引用 Soft Reference > 弱引用 Weak Reference > 虛引用 Phantom Reference
一個對象的生命周期:
如果有軟引用指向這些對象,則只有在JVM需要內存時才回收這些對象。
如果一個對象只有弱引用指向它,垃圾回收器會立即回收該對象,這是一種急切回收方式。
弱引用和軟引用的特殊行為使得它們在某些情況下非常有用。
例如:軟引用可以很好的用來實現緩存,當JVM需要內存時,垃圾回收器就會回收這些只有被軟引用指向的對象。
而弱引用非常適合存儲元數據,例如:存儲ClassLoader引用。如果沒有類被加載,那么也沒有指向ClassLoader的引用。一旦上一次的強引用被去除,只有弱引用的ClassLoader就會被回收
強引用
我們 new 出來的對象 “Object obj = new Object();”或者 String s=”abc”中變量s就是字符串對象”abc”的一個強引用,任何被強引用指向的對象都不能被垃圾回收器回收,這些對象都是在程序中需要的
軟引用
如果該對象含有軟引用,Counter對象不會立即被回收,除非JVM需要內存。
Java中的軟引用使用java.lang.ref.SoftReference類來表示
Counter prime = new Counter(); // prime holds a strong reference SoftReference soft = new SoftReference(prime) ; //soft reference variable has SoftReference to Counter Object created at line 2prime = null; // now Counter object is eligible for garbage collection but only be collected when JVM absolutely needs memory強引用置空之后,代碼的第二行為對象Counter創建了一個軟引用,該引用同樣不能阻止垃圾回收器回收對象,但是可以延遲回收,與弱引用中急切回收對象不同。
弱引用
只需要給強引用對象counter賦空值null,該對象就可以被垃圾回收器回收。因為該對象此時不再含有其他強引用,即使指向該對象的弱引用weakCounter也無法阻止垃圾回收器對該對象的回收。
弱引用使用java.lang.ref.WeakReference class 類來表示
Counter counter = new Counter(); // strong reference WeakReference<Counter> weakCounter = new WeakReference<Counter>(counter); //weak reference counter = null; // now Counter object is eligible for garbage collection另一個使用弱引用的例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一種實現。WeakHashMap有一個特點:map中的鍵值(keys)都被封裝成弱引用,也就是說一旦強引用被刪除,WeakHashMap內部的弱引用就無法阻止該對象被垃圾回收器回收。
虛引用
虛引用是java.lang.ref package包中第三種可用的引用,使用java.lang.ref.PhantomReference類來表示。擁有虛引用的對象可以在任何時候被垃圾回收器回收。
通過如下代碼創建虛引用:
DigitalCounter digit = new DigitalCounter(); // digit reference variable has strong reference – line 3 PhantomReference phantom = new PhantomReference(digit); // phantom reference to object created at line 3digit = null;一旦移除強引用,第三行的DigitalCounter對象可以在任何時候被垃圾回收器回收。因為只有一個虛引用指向該對象,而虛引用無法阻止垃圾回收器回收對象.
finalize 逃逸
原理
在使用可達性分析算法的虛機中,比如我們常用的hotspot, 當對象不可達時,需要至少經歷兩次標記過程,才能確定是否要回收。
實驗
package com.artisan.gc;public class FinalizeEscapeGC {public static FinalizeEscapeGC SAVE_HOOK = null;public void isAlive() {System.out.println("yes, I am still alive :) -- " + SAVE_HOOK);}// 重寫finalize方法,該方法只被調用一次,但并不是調用后立刻被回收@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");FinalizeEscapeGC.SAVE_HOOK = this;}public static void main(String[] args) throws InterruptedException {SAVE_HOOK = new FinalizeEscapeGC();/** 拯救成功*/SAVE_HOOK = null;// 提醒虛擬機進行垃圾回收,但是虛擬機具體什么時候進行回收就不知道了System.gc();Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("No, I am dead :(");}/** 拯救失敗*/SAVE_HOOK = null;System.gc();// finalize方法的優先級比較低所以等待它0.5秒Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("No, I am dead :(");}} }輸出:
finalize method executed! yes, I am still alive :) -- com.artisan.gc.FinalizeEscapeGC@5d888759 No, I am dead :(任何對象的finalize()方法只會被系統自動調用一次。
第一次逃脫成功,原因在于對象重寫了finalize()方法,在手動調用System.gc()時觸發垃圾回收,在執行finalize()方時, 在其中將 SAVE_HOOK重新用this關鍵字掛上和當前對象關系,所以在第二次標記時該對象已經不再“待回收”的隊列中了,所以此時對象還是存活的;
但是第二次逃亡的時候,不再執行了finalize()方法了(之前執行過一次,對象的finalize()方法必定只執行一次),在SAVE_HOOK至為null后不再可達,finalize()方法也是沒有必要執行的情況,所以它就直接為null了,沒有指向任何對象,此時對象已死。
注意事項
-
避免使用finalize(),操作不慎可能導致錯誤。
-
優先級低,何時被調用,不確定
-
何時發生GC不確定,自然也就不知道finalize方法什么時候執行
-
如果要使用finalize去釋放資源,我們可以使用try-catch-finally來替代它
回收方法區
很多人認為方法區(或者Hopspot虛機中的永久代)是沒有垃圾收集的,HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區 ,主要回收
- 廢棄常量
- 無用的類
廢棄常量的回收
常量池中除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值外,還包含一些以文本形式出現的符號引用,比如:
- 類和接口的全限定名;
- 字段的名稱和描述符;
- 方法和名稱和描述符。
回收廢棄常量和回收Java堆中的對象非常類似。 以常量池中的字面量的回收為例。
假設有一個字符串“abc”已經進入了常量池中,但當前系統中沒有任何一個String對象叫做“abc”的,換就話說就是沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生了內不曾能回收,而且有必要的話,這個“abc”就會被系統清理出常量池。 常量池中的其他類(接口)、方字段的符號引用也與此類似。
無用的類的回收
必須同時滿足如如下3個條件才能算是“無用的類”
- 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例
- 加載該類的ClassLoader已經被回收
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
即使同時滿足了如上3個條件,hotspot虛機也不一定必然回收,hotspot虛機提供了-Xnoclassgc參數進行控制。 還可以使用-verbose:class 以及-XX:+TraceClassLoading 、-XX:+TraceClassUnLoading查看類加載和卸載信息。
總結
以上是生活随笔為你收集整理的JVM-04垃圾收集Garbage Collection(上)【垃圾对象的判定】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM-03内存区域与内存溢出异常(下)
- 下一篇: JVM-05垃圾收集Garbage Co