java中如何判斷對象是否是垃圾
Java會自動進行內(nèi)存管理,JVM會進行垃圾回收,哪它是怎么判定哪些是“垃圾”并決定“垃圾”的生死呢?
判斷對象是否為“垃圾”
Java有兩種算法判斷對象是否是垃圾:引用計數(shù)算法和可達性分析算法。
引用計數(shù)算法
引用計數(shù)(Reference Counting)算法就是給對象加一個引用計數(shù)器,當對象被引用,計數(shù)器加一;當引用失效時,計數(shù)器減一;當對象的引用計數(shù)器為0,對象就會被視為垃圾。
優(yōu)點:
簡單、判定垃圾效率高。
缺點:
- 需要額外的空間存儲引用計數(shù)器
- 每當一個引用被賦值給另一個引用時,引用計數(shù)器就要進行調整,增加了賦值語句時間
- 會出現(xiàn)循環(huán)引用。比如,對象a引用了對象b,同時對象b也引用了對象a,這就導致兩個對象之間循環(huán)引用。對象a和對象b的引用都不為0,即使這兩個對象已經(jīng)沒有其他引用,由于它們的引用計數(shù)都大于0,所以它們就沒有辦法被回收。如果要解決這個問題就要引入額外機制,這樣效率又進一步降低了。
引用計數(shù)算法在當前主流的JVM中已經(jīng)沒有再被使用了。
簡單的例子測試一下
public class ReferenceCountingTest { public Object instance = null; // 10M 占用內(nèi)存,便于分析 private byte[] bytes = new byte[10*1024*1024]; public static void main(String[] args) { ReferenceCountingTest objectA = new ReferenceCountingTest(); ReferenceCountingTest objectB = new ReferenceCountingTest(); //互相引用 objectA.instance = objectB; objectB.instance = objectA; //切斷可達 objectA = null; objectB = null; //強制進行垃圾回收 System.gc(); } }
ReferenceCountingTest類中有一個10M的byte數(shù)組, 讓objectA.instance = objectB
和objectB.instance = objectA
導致objectA
和objectB
互相引用,如果采用引用計數(shù)法的話,這兩個對象是沒法辦法進行回收的,并且每個對象占用不少于10M的內(nèi)存空間。
在VM options 設置參數(shù) -XX:+PrintGC
打印GC情況,來看下運行結果是怎樣的:
[GC (System.gc()) 24381K->1106K(249344K), 0.0009894 secs] [Full GC (System.gc()) 1106K->957K(249344K), 0.0054511 secs]
從結果24381K->1106K
可以看到內(nèi)存從24381K
回收到1106K
,回收的空間差不多就是objectA
和objectB
兩個對象占用的空間。這也從側面說明JVM不是采用引用計數(shù)算法判定對象是否存活的。
可達性分析算法
可達性分析算法思路是使用一系列根對象(GC Roots)作為起點,從根節(jié)點開始向下進行搜索,搜索過的路徑稱為引用鏈(Reference Chain),如果某個對象到根節(jié)點沒有任何引用鏈相連或者說從根節(jié)點到這個對象不可達,則這個對象就被視為“垃圾”。
如上圖所示,白色橢圓形Object4、Object5、Object6之間雖然有關聯(lián),但是由于沒有和GC Roots關聯(lián),所以它們被判定為可回收對象。
在Java中有以下7種GC Roots:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象。比如:方法入?yún)?、局部變量?/li>
- 方法區(qū)中常量引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象:Java類的引用類型靜態(tài)變量
- 通過JNI調用本地代碼(nactive code)產(chǎn)生的JNI引用。包括JNI的局部變量或者全局變量
- 被系統(tǒng)類加載器加載的類,這些類不會被回收。它們可以以靜態(tài)字段的方式去持有對象。
- 所有被同步鎖(synchronized 關鍵)持有的對象
- 被JVM保留用于特殊目的的對象。哪些對象被保留取決于虛擬機的實現(xiàn),可能的有:系統(tǒng)類加載器、一些重要的異常類、做為異常類處理的被預分配對象或者一些自定義的類加載器。
以上8種GC Roots中前4個比較重要,在面試中也會經(jīng)常被問到,后3個了解一下即可。
可達性分析算法是目前在動態(tài)語言中使用最廣泛的算法,目前JVM判斷對象是否是垃圾用的都是這種算法。
垃圾的回收
Finalize方法
對象通過可達性分析算法被判定為可回收對象,也不是說對象一定要被回收,對象可以通過重寫finalize()
方法獲得一次“免死”機會。當發(fā)生GC的時候,JVM會判斷可回收的對象是否調用過finalize()
方法,如果調用過finalize()
方法,對象將會被回收;反之,如果沒有調用過 finalize()
方法,會將要調用finalize()
方法的對象 F-Queue
的隊列之中等待調用,在調用時如果對象重寫了finalize()
方法,可以在finalize()
方法中“托關系想辦法”讓自己和GC Roots搭上關系進行一次自我拯救,比如把自己(this關鍵字) 賦值給某個類變量或者對象的成員變量,對象就會從即將回收的列表中移除,這樣對象就完成了一次自我拯救。在執(zhí)行完finalize()
方法后,還會再判斷一次對象是否可達,如果不可達,自我拯救失敗,最后還是要被回收的。
要注意的一點是:對象finalize()
方法只會調用一次,如果對象自我拯救成功一次,當?shù)诙卧侔l(fā)生GC的時候會忽略調用對象的finalize()
方法,最后都要被回收。這就是JVM世界的法則,只給對象一次不成為垃圾的機會,如果再次成為垃圾,不好意思那只能被回收了。所以機會只有一次,要好好抓住。
下面通過例子測試一下對象的自我拯救:
public class FinalizeGC { private static Object instance; public static void main(String[] args) throws InterruptedException { instance = new FinalizeGC(); instance = null; //進行第一次垃圾回收 System.gc(); //休眠1s Thread.sleep(1000); if (instance != null) { System.out.println("I'm still alive."); }else { System.out.println("I'm dead."); } instance = null; //進行第二次垃圾回收 System.gc(); //休眠1s Thread.sleep(1000); if (instance != null) { System.out.println("I'm still alive."); }else { System.out.println("I'm dead."); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Override finalize method execute"); instance = this; } }
運行結果:
Override finalize method execute I'm still alive. I'm dead.
從運行結果可以看到對象只被自我拯救一次,第二次自我拯救失敗。
讓線程休眠Thread.sleep(1000)
1s是因為F-Queue
的隊列中的finalize()
方法,會由一條由虛擬機自動建立的、低調度優(yōu)先級的Finalizer
線程去執(zhí)行它們,休眠是為了等待Finalizer
線程去執(zhí)行finalize()
方法。
把Thread.sleep(1000)
注釋掉連續(xù)執(zhí)行多次,你可能會看到如下情況:
Override finalize method execute I'm dead. I'm dead.
或者
I'm dead. Override finalize method execute I'm dead.
出現(xiàn)上面的原因是finalize()
方法執(zhí)行緩慢,對象還沒有自我拯救就會回收了。所以finalize()
方法最好不要使用,太不可靠了,也不要想著用finalize()
方法進行自我拯救,finalize()
能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時。
方法區(qū)回收
方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類型?;厥諒U棄常量與回收Java堆中的對象非常類似。舉個常量池中字面量回收的例子,假如一個字符串“suncodernote”曾經(jīng)進入常量池中,但是當前系統(tǒng)又沒有任何一個字符串對象的值是“suncodernote”,換句話說,已經(jīng)沒有任何字符串對象引用常量池中的“suncodernote”常量,且虛擬機中也沒有其他地方引用這個字面量。如果在這時發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話,這個“suncodernote”常量就將會被系統(tǒng)清理出常量池。常量池中其他類(接口)、方法、字段的符號引用也與此類似。
判定一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了,必須同時滿足以下的條件(僅僅是可以,不代表必然,因為還有一些參數(shù)可以進行控制):
- 該類所有的實例都已經(jīng)被回收,也就是堆中不存在該類的任何實例。
- 加載該類的 ClassLoader 已經(jīng)被回收。
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法.
- 參數(shù)控制:-Xnoclassgc參數(shù)可以禁用類的垃圾收集(GC),這可以節(jié)省一些GC時間,從而縮短應用程序運行期間的中斷
到此這篇關于java中如何判斷對象是否是垃圾的文章就介紹到這了,更多相關java判斷垃圾內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MybatisPlus #{param}和${param}的用法詳解
這篇文章主要介紹了MybatisPlus #{param}和${param}的用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09Java抓包工具fiddler實現(xiàn)請求轉發(fā)
Fiddler是一個http協(xié)議調試代理工具,本文主要介紹了Java抓包工具fiddler實現(xiàn)請求轉發(fā),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04解決java使用axios.js的post請求后臺時無法接收到入?yún)⒌膯栴}
今天小編就為大家分享一篇解決java使用axios.js的post請求后臺時無法接收到入?yún)⒌膯栴},具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09詳解java爬蟲jsoup解析多空格class數(shù)據(jù)
在本篇內(nèi)容中小編給大家分享了java爬蟲jsoup怎么解析多空格class數(shù)據(jù)的方法和技巧,需要的朋友們跟著學習下。2018-12-12線程池ThreadPoolExecutor使用簡介與方法實例
今天小編就為大家分享一篇關于線程池ThreadPoolExecutor使用簡介與方法實例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03window?下?win10?jdk8安裝與環(huán)境變量的配置過程
這篇文章主要介紹了window?下?win10?jdk8安裝與環(huán)境變量的配置,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08