淺析Java虛擬機(jī)詳解之概述、對(duì)象生存法則
Java與C++之間有一堵由內(nèi)存分配和垃圾收集技術(shù)所圍成的高墻,墻外面的人想進(jìn)去,墻里面的人卻想出來。
一、概述
Java堆和方法區(qū)這兩個(gè)區(qū)域有著很顯著的不確定性:
1、一個(gè)接口的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能會(huì)不一樣,一個(gè)方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣
2、只有處于運(yùn)行期間,我們才能知道程序究竟會(huì)創(chuàng)建哪些對(duì)象,創(chuàng)建多少個(gè)對(duì)象,這部分內(nèi)存的分配和回收是動(dòng)態(tài)的
垃圾收集器所關(guān)注的正是這部分的內(nèi)存該如何管理
二、對(duì)象已死?
1、引用計(jì)數(shù)法
在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器就加一;當(dāng)引用失效時(shí),計(jì)數(shù)器就減一;任何時(shí)刻計(jì)數(shù)器為零的對(duì)象是不可能再被使用的。
引用計(jì)數(shù)器雖然占用了一些額外的內(nèi)存空間來進(jìn)行計(jì)數(shù),原理簡(jiǎn)單,判定效率很高;
為什么主流Java虛擬機(jī)沒有使用引用計(jì)數(shù)器來管理內(nèi)存呢?
引用計(jì)數(shù)法看似簡(jiǎn)單的算法有很多例外情況要考慮,必須配合大量額外處理才能保證正確的工作,比如單純的引用計(jì)數(shù)很難解決對(duì)象之間互相循環(huán)引用的問題。
引用計(jì)數(shù)器的缺陷
/** * @Author: yky * @CreateTime: 2020-12-13 * @Description: 引用計(jì)數(shù)器的缺陷 */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 這個(gè)成員變量唯一作用是占內(nèi)存 */ private byte[] bigSize = new byte[2 * _1MB]; public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; //發(fā)生GC,objA、objB能否被回收 System.gc(); } }
運(yùn)行代碼收查看日志信息發(fā)現(xiàn),這兩個(gè)對(duì)象均被回收虛擬機(jī)并沒有因?yàn)檫@兩個(gè)相互引用就放棄回收他們---->Java虛擬機(jī)并不是通過計(jì)數(shù)算法來判斷對(duì)象是否存活的;
2、可達(dá)性分析算法
該算法的核心思想:通過一系列稱為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,從這些結(jié)點(diǎn)開始,根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路徑稱為“引用鏈”,如果某個(gè)對(duì)象到GC Roots間沒有任何引用鏈相連(圖論話來說從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象是不可能再被使用的)
對(duì)象obj5、obj6、obj7雖然有關(guān)聯(lián),但是他們到GC roots不可達(dá)因此他們會(huì)被判定為可回收對(duì)象
在 Java 語(yǔ)言中,可作為 GC Roots 的對(duì)象包括以下幾種:
- 虛擬機(jī)棧(棧中的本地變量表)中的引用對(duì)象,如各線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時(shí)變量等;
- 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象,如Java類的引用類型靜態(tài)變量;
- 方法區(qū)中的常量引用的對(duì)象,如字符串常量里的引用;
- 本地方法??侸NI(Navicat方法)引用的對(duì)象;
- Java虛擬機(jī)內(nèi)部的引用
- 所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象
- 反應(yīng)Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等;
- 根據(jù)用戶所選的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域不同,還可以有其他對(duì)象“臨時(shí)性”地加入;
無論通過哪種算法判斷對(duì)象是否存活都和“引用”離不開關(guān)系。
1)強(qiáng)引用
是指在程序代碼之間普遍存在的引用賦值,Object obj = new Object();
這種引用關(guān)系。
無論什么情況下,只要強(qiáng)引用關(guān)系還在,垃圾收集器就不會(huì)回收掉被引用的對(duì)象;
2)軟引用
用來描述一些還有用,但非必須的對(duì)象。只要軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出前,會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收;如果這次的回收還沒有足夠的空間,才會(huì)拋出內(nèi)存溢出的異常;
JDK1.2后提供SoftReference
類實(shí)現(xiàn)軟引用:
Soft reference objects, which are cleared at the discretion of the garbage
collector in response to memory demand. Soft references are most often used
to implement memory-sensitive caches.
3)弱引用
弱引用也被用來描那些非必須對(duì)象,強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生為止,當(dāng)垃圾收集器開始工作,無論當(dāng)前內(nèi)存是足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象;
JDK1.2后WeakReference
類用來實(shí)現(xiàn)弱引用:
Weak reference objects, which do not prevent their referents from being
made finalizable, finalized, and then reclaimed. Weak references are most
often used to implement canonicalizing mappings.
4)虛引用
也叫“幽靈引用”、“幻影引用”,最弱的一種引用關(guān)系
- 一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無法通過虛引用獲得一個(gè)對(duì)象的實(shí)例;
- 為一個(gè)對(duì)象設(shè)置虛引用的唯一目的是為了能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知;
PhantomReference
類來實(shí)現(xiàn)虛引用:
Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed. Phantom references are most often used to schedule post-mortem cleanup actions.
應(yīng)用需要讀取大量本地圖片:
如果每次讀取圖片都從硬盤讀取,則會(huì)嚴(yán)重影響性能;解決方案:【軟引用或者弱引用】
Map<String,SoftReference<BitMap>> imp = new HashMap<String,SoftReference<BitMap>>
4、生存還是死亡?
在進(jìn)行過可達(dá)性分析后的對(duì)象也不一定是非死不可的,該對(duì)象進(jìn)行可達(dá)性分析后,發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈
- 這個(gè)對(duì)象就會(huì)第一次被標(biāo)記起來;對(duì)對(duì)象是否必要執(zhí)行finalize()方法進(jìn)行判斷(已經(jīng)被虛擬機(jī)調(diào)用過finalize()方法或者沒有覆蓋finalize()方法都認(rèn)為是沒有必要執(zhí)行該finalize()方法)
- F-Queue隊(duì)列中存放該對(duì)象,優(yōu)先級(jí)較低的Finalizer線程會(huì)去執(zhí)行它;Gc 會(huì)對(duì)這個(gè)隊(duì)列里面的對(duì)象再進(jìn)行一次標(biāo)記,如果在finalize方法中,對(duì)象沒有自己自救的話,它就會(huì)被標(biāo)記回收
- finalize方法自救自己的辦法是:重新與引用鏈上面的任何一個(gè)對(duì)象建立連接;如把自己this賦值給某個(gè)類或?qū)ο蟮某蓡T變量
/** 1.對(duì)象可以在GC時(shí)自救 2.自救的辦法只有一次,因?yàn)橐粋€(gè)finalize方法最多只能被調(diào)用一次 **/ 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 executed !"); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String [] args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGC(); //對(duì)象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); //因?yàn)閒inalize優(yōu)先級(jí)很低,所以延遲0.5s以等待它; Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no, i am dead :("); } //下面這段代碼再執(zhí)行一遍,驗(yàn)證對(duì)象是不是可以成功 SAVE_HOOK = null; System.gc(); Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no, i am dead :("); } } }
結(jié)果如下:
finalize method executed !
yes,I am still alive :)
no, i am dead :(
- 并不鼓勵(lì)使用這種辦法來拯救對(duì)象,它的運(yùn)行代價(jià)高昂,不確定性大,無法保證順序;
- finalize方法能做的所有工作,try-finally也可以做的更好,更及時(shí),所以希望忘記這個(gè)方法的存在;
5、回收方法區(qū)
很多人認(rèn)為方法區(qū)(或者HotSpot虛擬機(jī)中的元空間或永久代)是沒有垃圾收集行為的,《Java虛擬機(jī)規(guī)范》中確實(shí)說過可以不要求虛擬機(jī)在方法區(qū)實(shí)現(xiàn)垃圾收集,而且在方法區(qū)進(jìn)行垃圾收集的“性價(jià)比”一般比較低:在堆中,尤其是在新生代中,常規(guī)應(yīng)用進(jìn)行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠(yuǎn)低于此。
方法區(qū)的垃圾收集主要回收兩部分:廢棄的常量和不再使用的類型;
- 回收廢棄常量與回收J(rèn)ava堆中的對(duì)象非常類似。以常量池中字面量的回收為例:
假如一個(gè)字符串“Java”已經(jīng)進(jìn)入了常量池中,但是當(dāng)前系統(tǒng)沒有任何一個(gè)字符串對(duì)象的值是“Java”,換句話說是沒有任何String對(duì)象引用常量池中的“Java”常量,也沒有其他地方引用了這個(gè)字面量,如果在這時(shí)候發(fā)生內(nèi)存回收,而且必要的話,這個(gè)“Java”常量就會(huì)被系統(tǒng)清理出常量池。
- 常量池中的其他類(接口)、方法、字段的符號(hào)引用也與此類似。
判定一個(gè)常量是否是“廢棄常量”比較簡(jiǎn)單。而要判定一個(gè)類是否是“無用的類”的條件則相對(duì)苛刻許多。類需要同時(shí)滿足下面3個(gè)條件才能算是“無用的類”:
- 該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實(shí)例。
- 加載該類的ClassLoader已經(jīng)被回收。
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機(jī)可以對(duì)滿足上述3個(gè)條件的無用類進(jìn)行回收,這里說的僅僅是“被允許”,而不是和對(duì)象一樣,不使用了就必然會(huì)回收。在大量使用反射、動(dòng)態(tài)代理、CGLib等bytecode框架的場(chǎng)景,以及動(dòng)態(tài)生成JSP和OSGi這類頻繁自定義ClassLoader
的場(chǎng)景都需要虛擬機(jī)具備類卸載的功能,以保證不會(huì)被方法區(qū)造成過大的內(nèi)存壓力。
到此這篇關(guān)于Java虛擬機(jī)詳解之概述、對(duì)象生存法則的文章就介紹到這了,更多相關(guān)Java虛擬機(jī)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java虛擬機(jī)之類加載
- java虛擬機(jī)之JVM調(diào)優(yōu)詳解
- Java虛擬機(jī)內(nèi)存區(qū)域劃分詳解
- 概述java虛擬機(jī)中類的加載器及類加載過程
- 深入了解Java虛擬機(jī)棧以及內(nèi)存模型
- Java跨平臺(tái)原理與虛擬機(jī)相關(guān)簡(jiǎn)介
- java虛擬機(jī)jvm方法區(qū)實(shí)例講解
- java虛擬機(jī)鉤子關(guān)閉函數(shù)addShutdownHook的操作
- java虛擬機(jī)是做什么用的
- 詳解Java 虛擬機(jī)垃圾收集機(jī)制
- Java虛擬機(jī)使用jvisualvm工具遠(yuǎn)程監(jiān)控tomcat內(nèi)存
- Java虛擬機(jī)常見內(nèi)存溢出錯(cuò)誤匯總
- Java虛擬機(jī)執(zhí)行引擎知識(shí)總結(jié)
- Java啟用Azure Linux虛擬機(jī)診斷設(shè)置
- Java虛擬機(jī)內(nèi)存溢出與內(nèi)存泄漏
- java虛擬機(jī)創(chuàng)建失敗的原因整理
- Java內(nèi)存模型中的虛擬機(jī)棧原理分析
- Java基礎(chǔ)之創(chuàng)建虛擬機(jī)對(duì)象的過程詳細(xì)總結(jié)
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)單的推箱子小游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的推箱子小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Java的Spring框架中DAO數(shù)據(jù)訪問對(duì)象的使用示例
這篇文章主要介紹了Java的Spring框架中DAO數(shù)據(jù)訪問對(duì)象的使用示例,分為在Spring中DOA與JDBC以及與Hibernate的配合使用兩種情況來進(jìn)行演示,需要的朋友可以參考下2016-03-03java基于mongodb實(shí)現(xiàn)分布式鎖的示例代碼
本文主要介紹了java基于mongodb實(shí)現(xiàn)分布式鎖,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08吊打Java面試官之Lambda表達(dá)式 Stream API
這篇文章主要介紹了吊打Java之jdk8的新特性包括Lambda表達(dá)式、函數(shù)式接口、Stream API全面刨析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09SpringMVC 數(shù)據(jù)校驗(yàn)方法(必看篇)
下面小編就為大家?guī)硪黄猄pringMVC 數(shù)據(jù)校驗(yàn)方法(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06java新增關(guān)聯(lián)的三張表,每張表要求都插入集合,代碼實(shí)現(xiàn)方式
這篇文章主要介紹了java新增關(guān)聯(lián)的三張表,每張表要求都插入集合,代碼實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java詳細(xì)講解不同版本的接口語(yǔ)法和抽象類與接口的區(qū)別
對(duì)于面向?qū)ο缶幊虂碚f,抽象是它的一大特征之一,在?Java?中可以通過兩種形式來體現(xiàn)OOP的抽象:接口和抽象類,下面這篇文章主要給大家介紹了關(guān)于Java入門基礎(chǔ)之抽象類與接口的相關(guān)資料,需要的朋友可以參考下2022-04-04springBoot項(xiàng)目啟動(dòng)類啟動(dòng)無法訪問的解決方法
這篇文章主要介紹了springBoot項(xiàng)目啟動(dòng)類啟動(dòng)無法訪問的解決方法,需要的朋友可以參考下2018-10-10