十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
哪些對象可以被回收 徹底失去引用的對象? GC roots 可達性分析算法 可以判斷 沒有引用鏈的就可以被回收。這算第一次標(biāo)記。第二次標(biāo)記成功后會被回收。

馬龍ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!
對象在內(nèi)存中的狀態(tài):
可達狀態(tài):
可恢復(fù)狀態(tài):
不可達狀態(tài):
? ? ? ? ? ? ?
判斷對象是否存活都與引用有關(guān),下面介紹一下引用的分類:
引用分類:
強引用;Object obj = new Object()
軟引用;notmust
弱引用;
虛引用;
數(shù)組、對象
類似:Object obj = new Object()
只要存在強引用,垃圾收集器永遠不會回收掉被引用的對象
描述一些還有用但并非必須的對象。在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列入回收范圍之中進行第二次回收。如果這次回收之后還沒有足夠的內(nèi)存——拋出內(nèi)存異常
內(nèi)存充足,不會回收
內(nèi)存不足,回收
非必需對象
垃圾回收機制運行時,不管內(nèi)存充足與否,都會回收
不能單獨存在,必須和引用隊列聯(lián)合使用。
無法通過虛引用來去的一個對象實例。
對象存活判定算法:
引用計數(shù)算法:
思路:
給對象添加一個引用計數(shù)器,每當(dāng)一個地方引用它時,計數(shù)器加1;
當(dāng)引用失效時,計數(shù)器值就減1;
任何時刻計數(shù)器為0的對象就是不能再被使用的。
缺點:
很難解決對象之間相互循環(huán)引用的問題。導(dǎo)致他們的引用計數(shù)都不為0,于是引用計數(shù)算法無法通知GC收集器回收他們。
思路:
通過一系列的成為“GC Roots”的對象作為起始點,
從這些節(jié)點開始向下搜索,搜索所走過的路徑成為引用鏈(Reference Chain),
當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
應(yīng)用:
現(xiàn)在主流的商用程序語言(java,C#)都是通過可達性分析來判斷對象是否存活的。
Java中,可作為GC Roots的對象包括:
虛擬機棧(棧幀中的本地變量表)中引用的對象;
方法區(qū)中類靜態(tài)屬性引用的對象;
方法區(qū)中常量引用的對象;
本地方法棧中JNI(即Native方法)引用的對象;
對象死亡(被回收)前的最后一次掙扎
??即使在可達性分析算法中不可達的對象,也并非是“非死不可”,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標(biāo)記過程。
??第一次標(biāo)記:如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標(biāo)記;
??第二次標(biāo)記:第一次標(biāo)記后接著會進行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法。在finalize()方法中沒有重新與引用鏈建立關(guān)聯(lián)關(guān)系的,將被進行第二次標(biāo)記。
??第二次標(biāo)記成功的對象將真的會被回收,如果對象在finalize()方法中重新與引用鏈建立了關(guān)聯(lián)關(guān)系,那么將會逃離本次回收,繼續(xù)存活。
收集器?
淺談CMS垃圾收集器與G1收集器
Java之美[從菜鳥到高手演變]之JVM內(nèi)存管理及垃圾回收
jdk8:垃圾回收算法
1. 引用計數(shù)器算法
解釋
系統(tǒng)給每個對象添加一個引用計數(shù)器,每當(dāng)有一個地方引用這個對象的時候,計數(shù)器就加1,當(dāng)引用失效的時候,計數(shù)器就減1,在任何一個時刻計數(shù)器為0的對象就是不可能被使用的對象,因為沒有任何地方持有這個引用,這時這個對象就被視為內(nèi)存垃圾,等待被虛擬機回收
優(yōu)點
客觀的說,引用計數(shù)器算法,他的實現(xiàn)很簡單,判定的效率很高,在大部分情況下這都是相當(dāng)不錯的算法
其實,很多案例中都使用了這種算法,比如 IOS 的Object-C , 微軟的COM技術(shù)(用于給window開發(fā)驅(qū)動,.net里面的技術(shù)幾乎都是建立在COM上的),Python語言等.
缺陷
無法解決循環(huán)引用的問題.
這就好像是懸崖邊的人采集草藥的人, 想要活下去就必須要有一根繩子綁在懸崖上. 如果有兩個人, 甲的手拉著懸崖, 乙的手拉著甲, 那么這兩個人都能活, 但是, 如果甲的手拉著乙, 乙的手也拉著甲, 雖然這兩個人都認為自己被別人拉著, 但是一樣會掉下懸崖.
比如說 A對象的一個屬性引用B,B對象的一個屬性同時引用A A.b = B() B.a = A(); 這個A,B對象的計數(shù)器都是1,可是,如果沒有其他任何地方引用A,B對象的時候,A,B對象其實在系統(tǒng)中是無法發(fā)揮任何作用的,既然無法發(fā)揮作用,那就應(yīng)該被視作內(nèi)存垃圾予以清理掉,可是因為此時A,B的計數(shù)器的值都是1,虛擬機就無法回收A,B對象,這樣就會造成內(nèi)存浪費,這在計算機系統(tǒng)中是不可容忍的.
解決辦法
在語言層面處理, 例如Object-C 就使用強弱引用類型來解決問題.強引用計數(shù)器加1 ,弱引用不增加
Java中也有強弱引用
2. 可達性分析算法
解釋
這種算法通過一系列成為 "GC Roots " 的對象作為起始點,從這些節(jié)點開始向下搜索所有走過的路徑成為引用鏈(Reference Chain) , 當(dāng)一個對象GC Roots沒有任何引用鏈相連(用圖論的話來說就是從GC Roots到這個對象不可達),則證明此對象是不可用的
優(yōu)點
這個算法可以輕松的解決循環(huán)引用的問題
大部分的主流java虛擬機使用的都是這種算法
3. Java語言中的GC Roots
在虛擬機棧(其實是棧幀中的本地變量表)中引用的對象
在方法區(qū)中的類靜態(tài)屬性引用對象
在方法區(qū)中的常量引用的對象
在本地方法棧中JNI(即一般說的Native方法)的引用對象
相對于引用計數(shù)算法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高效等特點,更重要的是該算法可以有效解決引用計數(shù)算法中循環(huán)引用的問題,防止內(nèi)存泄漏的發(fā)生
java選擇的就是可達性分析算法,這種類型的垃圾收集通常也叫做追蹤性垃圾收集
所謂GC ROOTS 根集合就是一組必須活躍的引用
基本思路:
1.可達性分析算法是以根對象集合(GC Roots)為起始點,按照從上至下的方式搜索被根對象集合所連接的目標(biāo)對象是否可達
2.使用可達性分析算法后,內(nèi)存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈
3.如果目標(biāo)對象沒有任何引用鏈項鏈,則是不可達的,就意味著該對象已經(jīng)死亡,可以標(biāo)記為垃圾對象
4.在可達性分析算法中,只有能夠被根對象集合直接或者間接連接的對象才是存活對象
j當(dāng)面試扯到j(luò)vm這一部分的時候,面試官大概率會問你 jvm怎么判斷哪些對象應(yīng)該回收呢 ?
你會脫口而出 引用計數(shù)算法 和 可達性分析算法 。
引用計數(shù)法: 在對象中添加一個引用計數(shù)器,每當(dāng)一個地方引用它時,計數(shù)器就加一;當(dāng)引用失效時,計數(shù)器值就減一;任何時刻計數(shù)器為零的對象就是不可能再被使用的。
但是這樣的算法有一個問題?
就是不能解決循環(huán)依賴的問題。
可達性分析算法的思路 就是通過一系列的“GC Roots”,也就是根對象作為起始節(jié)點,從根節(jié)點開始,根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路徑稱為引用鏈,如果某個對象到GC Roots間沒有任何引用鏈相連。就是說從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的,是可以被回收的對象。
接下來面試官可能會問:
你剛剛談到了根節(jié)點,那你知道哪些對象可以作為根對象嗎?
你剛剛談到了引用,那你知道java里面有哪幾種引用嗎?
你剛剛談到了可達性分析算法,那如果在該算法中被判定不可達對象,是不是一定會被回收呢?
(這這些問題,文末會給出解答)
這些問題太常規(guī)了,本文要講一些不那么常見的: 并發(fā)標(biāo)記 , 浮動垃圾 。
CMS和G1都有一個并發(fā)標(biāo)記的過程,并發(fā)標(biāo)記要解決什么問題?帶來了什么問題?怎么解決這些問題呢?
剛剛我們談到的可達性分析算法是需要一個理論上的前提的: 該算法的全過程都需要基于一個能保障一致性的快照才能夠分析,這意味著必須全程凍結(jié)用戶線程的運行。 而為了不凍結(jié)用戶線程的運行,那我們就需要讓垃圾回收線程和用戶線程同時運行。
那我們先假設(shè)不并發(fā)標(biāo)記,即只有垃圾回收線程在運行的流程是怎樣的:
第一步:找到根節(jié)點,也就是我們常說的 根節(jié)點枚舉 。
在這個過程中,由于GC Roots是遠遠小與整個java堆中的全部對象,而且在OopMap此類優(yōu)化技巧的加持下,它帶來的停頓是非常短暫且固定的, 可以理解為不會隨著堆里的對象的增加而增加 ,如圖:
首先我們要搞清楚一個問題: 為什么遍歷對象圖的時候必須在一個能保證一致性的快照中?
為了說明這個問題,我們引入 “三色標(biāo)記” 方法。
什么是“三色標(biāo)記”?
在遍歷對象圖的過程中,把訪問的對象按照"是否訪問過"這個條件標(biāo)記成以下三種顏色:
白色:表示對象未被垃圾回收器訪問過 。
顯然可達性分析剛開始的時候,所有的對象都是白色,若在結(jié)束的時候,仍是白色的對象,即代表不可達。
黑色:表示已經(jīng)被垃圾回收器訪問過,且這個對象的所有引用都已經(jīng)掃描過
黑色的對象代表已經(jīng)掃描過,它是安全存活的,如果有其它的對象引用指向了黑色對象,無須重新掃描一遍。黑色對象不可能直接(不經(jīng)過灰色對象)指向某個白色對象。
灰色:表示已經(jīng)被垃圾回收器掃描過,但這個對象至少存在一個引用還沒有被掃描 。
如下圖所示:
我們先看一下正常標(biāo)記的過程:
首先是初始狀態(tài),很簡單,只有GC Roots是黑色的。同時需要注意下面的圖片的箭頭方向,代表的是有向的,比如其中的一條引用鏈是:
跟節(jié)點-5-6-7-8-11-10
如果在標(biāo)記的過程中,用戶線程修改了引用關(guān)系,就會出現(xiàn)下面的情況:
有一個大佬叫Wilson,他在1994年在理論上證明了, 只有同時滿足以下兩個條件時 ,會產(chǎn)生“對象消失”的問題,原來應(yīng)該是黑色的對象被標(biāo)記成了白色。
增量更新要破壞的是第一個條件(賦值器插入了一條或者多條從黑色對象到白色對象的新引用),當(dāng)黑色對象插入新的指向白色對象的引用關(guān)系時,就將這個新插入的引用 記錄下來 ,等并發(fā)掃描結(jié)束之后,再以這些記錄過的引用關(guān)系中的黑色對象為根, 重新掃描一次 。
可以簡化的理解為: 黑色對象一旦插入了指向白色對象的引用之后,它就變回了灰色對象 。
下面的圖就是一次并發(fā)掃描結(jié)束之后,記錄了黑色對象5新指向了白色對象9:
原始快照要破壞的是第二個條件(賦值器刪除了全部從灰色對象到該白色對象的直接或間接引用),當(dāng)灰色對象要刪除指向白色對象的引用關(guān)系時,就將這個要刪除的引用記錄下來,在并發(fā)掃描結(jié)束之后,再以這些記錄過的引用關(guān)系中的灰色對象為根,重新掃描一次。
可以簡化理解為:無論引用關(guān)系刪除與否,都會按照剛剛開始掃描那一刻的對象圖快照開進行搜索。
接下來回答一下,上面遺留的幾個問題:
GC管理的主要區(qū)域是Java堆,一般情況下只針對堆進行垃圾回收。方法區(qū)、棧和本地方法區(qū)不被GC所管理,因而選擇這些區(qū)域內(nèi)的對象作為GC Roots,被GC Roots引用的對象不被GC回收。
Class - 由系統(tǒng)類加載器(system class loader)加載的對象,這些類是不能夠被回收的,他們可以以靜態(tài)字段的方式保存持有其它對象。
Thread - 活著的線程
Stack Local - Java方法的local變量或者參數(shù)
JNI Local、JNI Global
Monitor Used - 用于同步的監(jiān)控對象
在Java語言里,可以作為GC Roots對象的包括如下幾種:
你知道java里面有哪幾種引用嗎?
在可達性分析算法判定為不可達的對象,是不是一定會被回收呢?
即使在可達性算法中不可達的對象也不一定是非死不可的,這時候它們暫時處于“緩刑”階段,要真正宣告它的死亡還需要經(jīng)歷兩次的標(biāo)記階段。
第一次標(biāo)記
在對象可達性算法不可達時,進行第一次標(biāo)記,并且進行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法。當(dāng)對象沒有覆蓋finalize方法或者該方法被虛擬機調(diào)用過,虛擬機將這兩種情況視為“沒有必要去執(zhí)行”,回收。
如果該對象被判定為有必要執(zhí)行finalize()方法,那么這個對象會被放置到一個叫做F-Queue的隊列中,并在稍后由一個虛擬機自動建立的、低優(yōu)先級的Finalize線程去執(zhí)行它。這里所謂的執(zhí)行就是去觸發(fā)該方法,但是并不會承諾等待它執(zhí)行結(jié)束,這樣做的原因是,如果對象在finalize()方法中執(zhí)行緩慢,或者發(fā)生死循環(huán),將會導(dǎo)致整個隊列中的對象處于等待之中。
第二次標(biāo)記
finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規(guī)模的標(biāo)記,如果對象要在finalize()中拯救自己——只要重新與引用鏈上的一個對象重新建立關(guān)聯(lián)即可,比如將自己(this關(guān)鍵字)賦值給某個類變量或者成員變量,那么在第二次標(biāo)記的時候就會被移除“即將回收”的集合;如果對象這時候還沒有逃脫,那么就會被真的回收了。
注意:第二次自救失敗是因為任何一個對象的finalize()方法只能執(zhí)行一次,如果第二次回收,就不會執(zhí)行finalize方法了!
(未完待續(xù))
GC Root在對象圖之外,是特別定義的“起點”,不可能被對象圖內(nèi)的對象所引用。
一個常見的誤解是以為GC Root是一組對象。
實際情況是GC Root通常是一組特別管理的指針,這些指針是tracing GC的trace的起點。它們不是對象圖里的對象,對象也不可能引用到這些“外部”的指針,所以題主想像的情況無法成立。
另外,tracing GC能正確處理循環(huán)引用,保證每個活對象只會被訪問一次就能確定其存活性。對象圖里是否存在循環(huán)引用,tracing GC都能正確判斷對象的存活與否。