16-垃圾回收相关概念
垃圾回收相關(guān)概念
System.gc()的理解
在默認(rèn)情況下,通過system.gc()者Runtime.getRuntime().gc() (即system.gc()底層實現(xiàn))的調(diào)用,會顯式觸發(fā)FullGC,同時對老年代和新生代及方法區(qū)進(jìn)行回收,嘗試釋放被丟棄對象占用的內(nèi)存。
然而system.gc() )調(diào)用附帶一個免責(zé)聲明,無法保證對垃圾收集器的調(diào)用。(不能確保立即生效)
JVM實現(xiàn)者可以通過system.gc() 調(diào)用來決定JVM的GC行為。而一般情況下,垃圾回收應(yīng)該是自動進(jìn)行的,無須手動觸發(fā),否則就太過于麻煩了。在一些特殊情況下,如我們正在編寫一個性能基準(zhǔn)測試,我們可以在運行之間調(diào)用System.gc()
代碼演示是否出發(fā)GC操作
/*** System.gc()*/ public class SystemGCTest {public static void main(String[] args) {new SystemGCTest();// 提醒JVM進(jìn)行垃圾回收,但不確定是否馬上執(zhí)行GCSystem.gc();//底層就是Runtime.getRuntime().gc() //System.runFinalization();}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("SystemGCTest 執(zhí)行了 finalize方法");} }運行結(jié)果,但是不一定會觸發(fā)銷毀的方法,調(diào)用System.runFinalization()會強(qiáng)制調(diào)用 失去引用對象的finalize()
SystemGCTest 執(zhí)行了 finalize方法手動GC來理解不可達(dá)對象的回收
代碼如下所示:
/*** 局部變量回收*/ public class LocalVarGC {/*** 觸發(fā)Minor GC沒有回收對象,然后在觸發(fā)Full GC將該對象存入old區(qū)*/public void localvarGC1() {byte[] buffer = new byte[10*1024*1024];System.gc();}/*** 觸發(fā)YoungGC的時候,已經(jīng)被回收了*/public void localvarGC2() {byte[] buffer = new byte[10*1024*1024];buffer = null;System.gc();}/*** 不會被回收,因為它還存放在局部變量表索引為1的槽中。*/public void localvarGC3() {{byte[] buffer = new byte[10*1024*1024];}System.gc();}/*** 會被回收,因為它還存放在局部變量表索引為1的槽中,但是后面定義的value把這個槽給替換了,所以就沒有引用指向buffer*/public void localvarGC4() {{byte[] buffer = new byte[10*1024*1024];}int value = 10;System.gc();}/*** localvarGC5中的數(shù)組已經(jīng)被回收。localvarGC1的GC執(zhí)行完后,大對象已經(jīng)晉升到了老年區(qū),再次調(diào)用GC就會直接被回收了。*/public void localvarGC5() {localvarGC1();System.gc();}public static void main(String[] args) {LocalVarGC localVarGC = new LocalVarGC();localVarGC.localvarGC3();} }內(nèi)存溢出(OOM)
內(nèi)存溢出相對于內(nèi)存泄漏來說,盡管更容易被理解,但是同樣的,內(nèi)存溢出也是引發(fā)程序崩潰的罪魁禍?zhǔn)字弧?/p>
由于GC一直在發(fā)展,所有一般情況下,除非應(yīng)用程序占用的內(nèi)存增長速度非常快,造成垃圾回收已經(jīng)跟不上內(nèi)存消耗的速度,否則不太容易出現(xiàn)ooM的情況。
大多數(shù)情況下,GC會進(jìn)行各種年齡段的垃圾回收,實在不行了就放大招,來一次獨占式的Fu11GC操作,這時候會回收大量的內(nèi)存,供應(yīng)用程序繼續(xù)使用。
javadoc中對outofMemoryError的解釋是,沒有空閑內(nèi)存,并且垃圾收集器也無法提供更多內(nèi)存。
首先說沒有空閑內(nèi)存的情況:說明Java虛擬機(jī)的堆內(nèi)存不夠。原因有二:
- Java虛擬機(jī)的堆內(nèi)存設(shè)置不夠。
比如:可能存在內(nèi)存泄漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的數(shù)據(jù)量,但是沒有顯式指定JVM堆大小或者指定數(shù)值偏小。我們可以通過參數(shù)-Xms 、-Xmx來調(diào)整。
- 代碼中創(chuàng)建了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)
對于老版本的oracle JDK,因為永久代的大小是有限的,并且JVM對永久代垃圾回收(如,常量池回收、卸載不再需要的類型)非常不積極,所以當(dāng)我們不斷添加新類型的時候,永久代出現(xiàn)OutOfMemoryError也非常多見,尤其是在運行時存在大量動態(tài)類型生成的場合;類似intern字符串緩存占用太多空間,也會導(dǎo)致OOM問題。對應(yīng)的異常信息,會標(biāo)記出來和永久代相關(guān):“java.lang.OutOfMemoryError:PermGen space"。
隨著元數(shù)據(jù)區(qū)的引入,方法區(qū)內(nèi)存已經(jīng)不再那么窘迫,所以相應(yīng)的ooM有所改觀,出現(xiàn)ooM,異常信息則變成了:“java.lang.OutofMemoryError:Metaspace"。直接內(nèi)存不足,也會導(dǎo)致OOM。
這里面隱含著一層意思是,在拋出OutofMemoryError之前,通常垃圾收集器會被觸發(fā),盡其所能去清理出空間。
例如:在引用機(jī)制分析中,涉及到JVM會去嘗試回收軟引用指向的對象等。
在java.nio.BIts.reserveMemory()方法中,我們能清楚的看到,System.gc()會被調(diào)用,以清理空間。
當(dāng)然,也不是在任何情況下垃圾收集器都會被觸發(fā)的
比如,我們?nèi)シ峙湟粋€超大對象,類似一個超大數(shù)組超過堆的最大值,JVM可以判斷出垃圾收集并不能解決這個問題,所以直接拋出OutofMemoryError。
內(nèi)存泄漏
也稱作“存儲滲漏”。嚴(yán)格來說,只有對象不會再被程序用到了,但是GC又不能回收他們的情況,才叫內(nèi)存泄漏。
但實際情況很多時候一些不太好的實踐(或疏忽)會導(dǎo)致對象的生命周期變得很長甚至導(dǎo)致00M,也可以叫做寬泛意義上的“內(nèi)存泄漏”。
盡管內(nèi)存泄漏并不會立刻引起程序崩潰,但是一旦發(fā)生內(nèi)存泄漏,程序中的可用內(nèi)存就會被逐步蠶食,直至耗盡所有內(nèi)存,最終出現(xiàn)outofMemory異常,導(dǎo)致程序崩潰。
注意,這里的存儲空間并不是指物理內(nèi)存,而是指虛擬內(nèi)存大小,這個虛擬內(nèi)存大小取決于磁盤交換區(qū)設(shè)定的大小。
買房子:80平的房子,但是有10平是公攤的面積,我們是無法使用這10平的空間,這就是所謂的內(nèi)存泄漏
Java使用可達(dá)性分析算法,最上面的數(shù)據(jù)不可達(dá),就是需要被回收的。后期有一些對象不用了,按道理應(yīng)該斷開引用,但是存在一些鏈沒有斷開,從而導(dǎo)致沒有辦法被回收。
內(nèi)存泄漏舉例
- 單例模式
單例的生命周期和應(yīng)用程序是一樣長的,所以單例程序中,如果持有對外部對象的引用的話,那么這個外部對象是不能被回收的,則會導(dǎo)致內(nèi)存泄漏的產(chǎn)生。
- 一些提供close的資源未關(guān)閉導(dǎo)致內(nèi)存泄漏
數(shù)據(jù)庫連接(dataSourse.getConnection() ),網(wǎng)絡(luò)連接(socket)和io連接必須手動close,否則是不能被回收的。
Stop The World
stop-the-world,簡稱STW,指的是GC事件發(fā)生過程中,會產(chǎn)生應(yīng)用程序的停頓。停頓產(chǎn)生時整個應(yīng)用程序線程都會被暫停,沒有任何響應(yīng),有點像卡死的感覺,這個停頓稱為STW。
-
可達(dá)性分析算法中枚舉根節(jié)點(GC Roots)會導(dǎo)致所有Java執(zhí)行線程停頓。(因為GC Roots是在變化的)
-
分析工作必須在一個能確保一致性的快照中進(jìn)行
-
一致性指整個分析期間整個執(zhí)行系統(tǒng)看起來像被凍結(jié)在某個時間點上
-
如果出現(xiàn)分析過程中對象引用關(guān)系還在不斷變化,則分析結(jié)果的準(zhǔn)確性無法保證
-
被STW中斷的應(yīng)用程序線程會在完成GC之后恢復(fù),頻繁中斷會讓用戶感覺像是網(wǎng)速不快造成電影卡帶一樣,所以我們需要減少STW的發(fā)生。
STW事件和采用哪款GC無關(guān)所有的GC都有這個事件。
哪怕是G1也不能完全避免Stop-the-world情況發(fā)生,只能說垃圾回收器越來越優(yōu)秀,回收效率越來越高,盡可能地縮短了暫停時間。
STW是JVM在后臺自動發(fā)起和自動完成的。在用戶不可見的情況下,把用戶正常的工作線程全部停掉。
開發(fā)中不要用System.gc() ;會導(dǎo)致stop-the-world的發(fā)生。
垃圾回收的并行與并發(fā)
并發(fā)
在操作系統(tǒng)中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理器上運行。
并發(fā)不是真正意義上的“同時進(jìn)行”,只是CPU把一個時間段劃分成幾個時間片段(時間區(qū)間),然后在這幾個時間區(qū)間之間來回切換,由于CPU處理的速度非常快,只要時間間隔處理得當(dāng),即可讓用戶感覺是多個應(yīng)用程序同時在進(jìn)行。
并行
當(dāng)系統(tǒng)有一個以上CPU時,當(dāng)一個CPU執(zhí)行一個進(jìn)程時,另一個CPU可以執(zhí)行另一個進(jìn)程,兩個進(jìn)程互不搶占CPU資源,可以同時進(jìn)行,我們稱之為并行(Paralle1)。
其實決定并行的因素不是CPU的數(shù)量,而是CPU的核心數(shù)量,比如一個CPU多個核也可以并行。
適合科學(xué)計算,后臺處理等弱交互場景
并發(fā)和并行對比
并發(fā),指的是多個事情,在同一時間段內(nèi)同時發(fā)生了。
并行,指的是多個事情,在同一時間點上同時發(fā)生了。
并發(fā)的多個任務(wù)之間是互相搶占資源的。并行的多個任務(wù)之間是不互相搶占資源的。
只有在多CPU或者一個CPU多核的情況中,才會發(fā)生并行。
否則,看似同時發(fā)生的事情,其實都是并發(fā)執(zhí)行的。
垃圾回收的并行與并發(fā)
并發(fā)和并行,在談?wù)摾占鞯纳舷挛恼Z境中,它們可以解釋如下:
-
并行(Parallel):指多條垃圾收集線程并行工作,但此時用戶線程仍處于等待狀態(tài)。
- 如ParNew、Parallel Scavenge、Parallel old;
-
串行(Serial)
- 相較于并行的概念,單線程執(zhí)行。
- 如果內(nèi)存不夠,則程序暫停,啟動JM垃圾回收器進(jìn)行垃圾回收。回收完,再啟動程序的線程。
并發(fā)和并行,在談?wù)摾占鞯纳舷挛恼Z境中,它們可以解釋如下:
- 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行,并發(fā),指的是多個事情,在同一時間段內(nèi)同時發(fā)生了)
- 如:CMS、G1,追求切換的低延遲
- 并行:垃圾回收線程在執(zhí)行時不會停頓用戶程序的運行。用戶程序在繼續(xù)運行,而垃圾收集程序線程運行于另一個CPU上;
安全點與安全區(qū)域
安全點(Safepoint)
程序執(zhí)行時并非在所有地方都能停頓下來開始GC,只有在特定的位置才能停頓下來開始GC,這些位置稱為“安全點(Safepoint)”。
Safe Point的選擇很重要,如果太少可能導(dǎo)致GC等待的時間太長,如果太頻繁可能導(dǎo)致運行時的性能問題。大部分指令的執(zhí)行時間都非常短暫,通常會根據(jù)**“是否具有讓程序長時間執(zhí)行的特征”為標(biāo)準(zhǔn)。比如:選擇一些執(zhí)行時間較長的指令作為Safe Point,如方法調(diào)用、循環(huán)跳轉(zhuǎn)和異常跳轉(zhuǎn)**等。
如何在GC發(fā)生時,檢查所有線程都跑到最近的安全點停頓下來呢?
- 搶先式中斷:(目前沒有虛擬機(jī)采用了) 首先中斷所有線程。如果還有線程不在安全點,就恢復(fù)線程,讓線程跑到安全點。
- 主動式中斷:設(shè)置一個中斷標(biāo)志,各個線程運行到Safe Point的時候主動輪詢這個標(biāo)志,如果中斷標(biāo)志為真,則將自己進(jìn)行中斷掛起。(有輪詢的機(jī)制)
安全區(qū)域(Safe Region)
Safepoint 機(jī)制保證了程序執(zhí)行時,在不太長的時間內(nèi)就會遇到可進(jìn)入GC的Safepoint。但是,程序“不執(zhí)行”的時候呢?例如線程處于Sleep狀態(tài)或Blocked 狀態(tài),這時候線程無法響應(yīng)JVM的中斷請求,“走”到安全點去中斷掛起,JVM也不太可能等待線程被喚醒。對于這種情況,就需要安全區(qū)域(Safe Region)來解決。
**安全區(qū)域是指在一段代碼片段中,對象的引用關(guān)系不會發(fā)生變化,在這個區(qū)域中的任何位置開始GC都是安全的。**我們也可以把Safe Region看做是被擴(kuò)展了的Safepoint。
執(zhí)行流程:
- 當(dāng)線程運行到Safe Region的代碼時,首先標(biāo)識已經(jīng)進(jìn)入了Safe Relgion,如果這段時間內(nèi)發(fā)生GC,JVM會忽略標(biāo)識為Safe Region狀態(tài)的線程,即不執(zhí)行Safe Region狀態(tài)的線程
- 當(dāng)線程即將離開Safe Region時,會檢查JVM是否已經(jīng)完成GC,如果完成了,則繼續(xù)運行,否則線程必須等待直到收到可以安全離開Safe Region的信號為止;
再談引用
我們希望能描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時,則能保留在內(nèi)存中;如果內(nèi)存空間在進(jìn)行垃圾收集后還是很緊張,則可以拋棄這些對象。
【既偏門又非常高頻的面試題】強(qiáng)引用、軟引用、弱引用、虛引用有什么區(qū)別?具體使用場景是什么?
在JDK1.2版之后,Java對引用的概念進(jìn)行了擴(kuò)充,將引用分為:
- 強(qiáng)引用(Strong Reference)
- 軟引用(Soft Reference)
- 弱引用(Weak Reference)
- 虛引用(Phantom Reference)
這4種引用強(qiáng)度依次逐漸減弱。除強(qiáng)引用外,其他3種引用均可以在java.lang.ref包中找到它們的身影。如下圖,顯示了這3種引用類型對應(yīng)的類,開發(fā)人員可以在應(yīng)用程序中直接使用它們。
Reference子類中只有終結(jié)器引用是包內(nèi)可見的,其他3種引用類型均為public,可以在應(yīng)用程序中直接使用
- 強(qiáng)引用(StrongReference):最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“object obj=new Object()”這種引用關(guān)系。無論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會回收掉被引用的對象。
- —>死也不回收
- 使用場景:99%都是強(qiáng)引用
- 軟引用(SoftReference):在系統(tǒng)將要發(fā)生內(nèi)存溢出之前,將會把這些對象列入回收范圍之中進(jìn)行第二次回收。如果這次回收后還沒有足夠的內(nèi)存,才會拋出內(nèi)存流出異常。
- —>內(nèi)存不足即回收
- 使用場景:緩存
- 弱引用(WeakReference):被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集之前。當(dāng)垃圾收集器工作時,無論內(nèi)存空間是否足夠,都會回收掉被弱引用關(guān)聯(lián)的對象。
- —>發(fā)現(xiàn)即回收
- 使用場景:緩存
- 虛引用(PhantomReference):一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來獲得一個對象的實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知,常用于對象回收的跟蹤。
- 對象回收跟蹤
- 使用場景:在這個對象被收集器回收時收到一個系統(tǒng)通知,常用于對象回收的跟蹤
再談引用:強(qiáng)引用——死也不回收
在Java程序中,最常見的引用類型是強(qiáng)引用(普通系統(tǒng)99%以上都是強(qiáng)引用),也就是我們最常見的普通對象引用,也是默認(rèn)的引用類型。
當(dāng)在Java語言中使用new操作符創(chuàng)建一個新的對象,并將其賦值給一個變量的時候,這個變量就成為指向該對象的一個強(qiáng)引用。
強(qiáng)引用的對象是可觸及的,即從GC Roots可達(dá)的,垃圾收集器就永遠(yuǎn)不會回收掉被引用的對象。
對于一個普通的對象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為null,就是可以當(dāng)做垃圾被收集了,當(dāng)然具體回收時機(jī)還是要看垃圾收集策略。
相對的,軟引用、弱引用和虛引用的對象是軟可觸及、弱可觸及和虛可觸及的,在一定條件下,都是可以被回收的。所以,強(qiáng)引用是造成Java內(nèi)存泄漏的主要原因之一。(但注意,他們都是可觸及的。)
舉例
強(qiáng)引用的案例說明
StringBuffer str = new StringBuffer("hello mogublog");局部變量str指向stringBuffer實例所在堆空間,通過str可以操作該實例,那么str就是stringBuffer實例的強(qiáng)引用對應(yīng)內(nèi)存結(jié)構(gòu):
如果此時,在運行一個賦值語句
StringBuffer str = new StringBuffer("hello mogublog"); StringBuffer str1 = str;對應(yīng)的內(nèi)存結(jié)構(gòu)為:
那么我們將 str = null; 則 原來堆中的對象也不會被回收,因為還有其它對象指向該區(qū)域
總結(jié)
本例中的兩個引用,都是強(qiáng)引用,強(qiáng)引用具備以下特點:
- 強(qiáng)引用可以直接訪問目標(biāo)對象。
- 強(qiáng)引用所指向的對象在任何時候都不會被系統(tǒng)回收,虛擬機(jī)寧愿拋出OOM異常,也不會回收強(qiáng)引用所指向?qū)ο蟆?/li>
- 強(qiáng)引用可能導(dǎo)致內(nèi)存泄漏。
再談引用: 軟引用——內(nèi)存不足即回收
軟引用是用來描述一些還有用,但非必需的對象。只被軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。(即第一次將不可達(dá)的對象回收后,空間仍是不足,那么接下來我就要將軟引用回收)
注意,這里的第一次回收是不可達(dá)的對象,即沒有引用指向他了
軟引用通常用來實現(xiàn)內(nèi)存敏感的緩存。比如:高速緩存就有用到軟引用。如果還有空閑內(nèi)存,就可以暫時保留緩存,當(dāng)內(nèi)存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內(nèi)存。
垃圾回收器在某個時刻決定回收軟可達(dá)的對象的時候,會清理軟引用,并可選地把引用存放到一個引用隊列(Reference Queue)。
類似弱引用,只不過Java虛擬機(jī)會盡量讓軟引用的存活時間長一些,迫不得已才清理。
一句話概括:當(dāng)內(nèi)存足夠時,不會回收軟引用可達(dá)的對象。內(nèi)存不夠時,會回收軟引用的可達(dá)對象
在JDK1.2版之后提供了SoftReference類來實現(xiàn)軟引用
// 聲明強(qiáng)引用 Object obj = new Object(); // 創(chuàng)建一個軟引用 SoftReference<Object> sf = new SoftReference<>(obj); obj = null; //銷毀強(qiáng)引用,這是必須的,不然會存在強(qiáng)引用和軟引用再談引用:弱引用—發(fā)現(xiàn)即回收
發(fā)現(xiàn)即回收
弱引用也是用來描述那些非必需對象,只被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止。在系統(tǒng)GC時,只要發(fā)現(xiàn)弱引用,不管系統(tǒng)堆空間使用是否充足,都會回收掉只被弱引用關(guān)聯(lián)的對象。
但是,由于垃圾回收器的線程通常優(yōu)先級很低,因此,并不一定能很快地發(fā)現(xiàn)持有弱引用的對象。在這種情況下,弱引用對象可以存在較長的時間。
弱引用和軟引用一樣,在構(gòu)造弱引用時,也可以指定一個引用隊列,當(dāng)弱引用對象被回收時,就會加入指定的引用隊列,通過這個隊列可以跟蹤對象的回收情況。
**軟引用、弱引用都非常適合來保存那些可有可無的緩存數(shù)據(jù)。**如果這么做,當(dāng)系統(tǒng)內(nèi)存不足時,這些緩存數(shù)據(jù)會被回收,不會導(dǎo)致內(nèi)存溢出。而當(dāng)內(nèi)存資源充足時,這些緩存數(shù)據(jù)又可以存在相當(dāng)長的時間,從而起到加速系統(tǒng)的作用。
在JDK1.2版之后提供了WeakReference類來實現(xiàn)弱引用
// 聲明強(qiáng)引用 Object obj = new Object(); // 創(chuàng)建一個弱引用 WeakReference<Object> sf = new WeakReference<>(obj); obj = null; //銷毀強(qiáng)引用,這是必須的,不然會存在強(qiáng)引用和弱引用弱引用對象與軟引用對象的最大不同就在于,當(dāng)GC在進(jìn)行回收時,需要通過算法檢查是否回收軟引用對象,而對于弱引用對象,GC總是進(jìn)行回收。弱引用對象更容易、更快被GC回收。
面試題:你開發(fā)中使用過WeakHashMap嗎?
WeakHashMap用來存儲圖片信息,可以在內(nèi)存不足的時候,及時回收,避免了OOM
再談引用:虛引用——對象回收跟蹤
也稱為“幽靈引用”或者“幻影引用”,是所有引用類型中最弱的一個
一個對象是否有虛引用的存在,完全不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它和沒有引用幾乎是一樣的,隨時都可能被垃圾回收器回收。
它不能單獨使用,也無法通過虛引用來獲取被引用的對象。當(dāng)試圖通過虛引用的get()方法取得對象時,總是null
為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的在于跟蹤垃圾回收過程。比如:能在這個對象被收集器回收時收到一個系統(tǒng)通知。
虛引用必須和引用隊列一起使用。虛引用在創(chuàng)建時必須提供一個引用隊列作為參數(shù)。當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象后,將這個虛引用加入引用隊列,以通知應(yīng)用程序?qū)ο蟮幕厥涨闆r。
由于虛引用可以跟蹤對象的回收時間,因此,也可以將一些資源釋放操作放置在虛引用中執(zhí)行和記錄。
虛引用無法獲取到我們的數(shù)據(jù)
在JDK1.2版之后提供了PhantomReference類來實現(xiàn)虛引用。
// 聲明強(qiáng)引用 Object obj = new Object(); // 聲明引用隊列 ReferenceQueue phantomQueue = new ReferenceQueue(); // 聲明虛引用(還需要傳入引用隊列) PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue); obj = null;案例
我們使用一個案例,來結(jié)合虛引用,引用隊列,finalize進(jìn)行講解
public class PhantomReferenceTest {// 當(dāng)前類對象的聲明public static PhantomReferenceTest obj;// 引用隊列static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("調(diào)用當(dāng)前類的finalize方法");obj = this;//會使得當(dāng)前對象復(fù)活}public static void main(String[] args) {Thread thread = new Thread(() -> {while(true) {if (phantomQueue != null) {PhantomReference<PhantomReferenceTest> objt = null;try {objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();} catch (Exception e) {e.getStackTrace();}if (objt != null) {System.out.println("追蹤垃圾回收過程:PhantomReferenceTest實例被GC了");}}}}, "t1");thread.setDaemon(true);thread.start();phantomQueue = new ReferenceQueue<>();obj = new PhantomReferenceTest();// 構(gòu)造了PhantomReferenceTest對象的虛引用,并指定了引用隊列PhantomReference<PhantomReferenceTest> phantomReference = new PhantomReference<>(obj, phantomQueue);try {//不可獲取虛引用中的對象System.out.println(phantomReference.get());// 去除強(qiáng)引用obj = null;// 第一次進(jìn)行GC,由于對象可復(fù)活,GC無法回收該對象System.out.println("第一次GC操作");System.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 不是 null");}System.out.println("第二次GC操作");obj = null;System.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 不是 null");}} catch (Exception e) {e.printStackTrace();} finally {}} }最后運行結(jié)果
null 第一次GC操作 調(diào)用當(dāng)前類的finalize方法 obj 不是 null 第二次GC操作 追蹤垃圾回收過程:PhantomReferenceTest實例被GC了 obj 是 null從上述運行結(jié)果我們知道,第一次嘗試獲取虛引用的值,發(fā)現(xiàn)無法獲取的,這是因為虛引用是無法直接獲取對象的值,然后進(jìn)行第一次gc,因為會調(diào)用finalize方法,將對象復(fù)活了,所以對象沒有被回收,但是調(diào)用第二次gc操作的時候,因為finalize方法只能執(zhí)行一次,所以就觸發(fā)了GC操作,將對象回收了,同時將會觸發(fā)第二個操作就是 將回收的值存入到引用隊列中。
終結(jié)器引用(了解)
它用于實現(xiàn)對象的finalize() 方法,也可以稱為終結(jié)器引用
無需手動編碼,其內(nèi)部配合引用隊列使用
在GC時,終結(jié)器引用入隊。由Finalizer線程通過終結(jié)器引用找到被引用對象調(diào)用它的finalize()方法,第二次GC時才回收被引用的對象
總結(jié)
以上是生活随笔為你收集整理的16-垃圾回收相关概念的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 15-垃圾回收相关算法
- 下一篇: 17-垃圾回收器