深入JVM內(nèi)核12 JVM垃圾回收

1. 如何判斷對象可以被回收

堆中幾乎放著所有的對象實(shí)例,對堆垃圾回收前的第一步就是要判斷那些對象已經(jīng)死亡(即不能再被任何途徑使用的對象)。

1.1 引用計(jì)數(shù)法

實(shí)現(xiàn)思路:給對象添加一個引用計(jì)數(shù)器肮砾。每當(dāng)有一個地方引用它時残吩,計(jì)數(shù)器加1生棍;引用失效時計(jì)數(shù)器減1。在任何時刻計(jì)數(shù)器為0的對象就是不可能再被使用的猪钮。

優(yōu)點(diǎn):實(shí)現(xiàn)簡單,效率高胆建。

缺點(diǎn):很難解決對象之間的相互循環(huán)引用烤低。A引用B,B引用A ——循環(huán)引用 (引用計(jì)數(shù)算法)由于A笆载、B彼此引用對方扑馁,導(dǎo)致引用計(jì)數(shù)都不為0涯呻,所以GC無法回收它們

 1 public class MyObject {
 2     public Object ref = null;
 3     public static void main(String[] args) {
 4         MyObject myObject1 = new MyObject();
 5         MyObject myObject2 = new MyObject();
 6         myObject1 = myObject2;
 7         myObject2 = myObject1;
 8         myObject1 = null;
 9         myObject2 = null;
10     }
11 }

以myObject1對象為例:
1、代碼執(zhí)行到line7腻要,myObject1复罐、myObject2的引用計(jì)數(shù)均為2。

2雄家、此時將myObject1和myObject2分別置為null效诅,以前一個對象為例,它的引用計(jì)數(shù)將減1趟济。

3乱投、當(dāng)myObject1=0,垃圾回收器才能進(jìn)行垃圾回收顷编,由于 myObject2 = myObject1;因?yàn)閙yObject2持有myObject1的引用戚炫,而要清除掉這個引用的前提條件是myObject2引用的對象被回收。而myObject2的引用又被myObject1持有媳纬,也就進(jìn)入一種死循環(huán)的狀態(tài)双肤。

4、最終myObject1 和myObject2引用計(jì)數(shù)均為1层宫,導(dǎo)致不能進(jìn)行垃圾回收杨伙。

1.2 可達(dá)性分析算法

這個算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索萌腿,節(jié)點(diǎn)所走過的路徑稱為引用鏈限匣,當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的毁菱。
GC Roots根節(jié)點(diǎn):類加載器米死、Thread、虛擬機(jī)棧的本地變量表贮庞、static成員峦筒、常量引用、本地方法棧的變量等等


image.png
1.3 finalize()方法最終判定對象是否存活

即使在可達(dá)性分析算法中不可達(dá)的對象窗慎,也并非是“非死不可”的物喷,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡遮斥,至少要經(jīng)歷再次標(biāo)記過程峦失。
標(biāo)記的前提是對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。

  1. 第一次標(biāo)記并進(jìn)行一次篩選术吗。
    篩選的條件是此對象是否有必要執(zhí)行finalize()方法尉辑。
    當(dāng)對象沒有覆蓋finalize方法,或者finzlize方法已經(jīng)被虛擬機(jī)調(diào)用過较屿,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”隧魄,對象被回收卓练。
  2. 第二次標(biāo)記
    如果這個對象被判定為有必要執(zhí)行finalize()方法,那么這個對象將會被放置在一個名為:F-Queue的隊(duì)列之中购啄,并在稍后由一條虛擬機(jī)自動建立的襟企、低優(yōu)先級的Finalizer線程去執(zhí)行。這里所謂的“執(zhí)行”是指虛擬機(jī)會觸發(fā)這個方法闸溃,但并不承諾會等待它運(yùn)行結(jié)束整吆。這樣做的原因是,如果一個對象finalize()方法中執(zhí)行緩慢辉川,或者發(fā)生死循環(huán)(更極端的情況)表蝙,將很可能會導(dǎo)致F-Queue隊(duì)列中的其他對象永久處于等待狀態(tài),甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰乓旗。
    finalize()方法是對象脫逃死亡命運(yùn)的最后一次機(jī)會府蛇,稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模標(biāo)記,如果對象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個對象建立關(guān)聯(lián)即可屿愚,譬如把自己賦值給某個類變量或?qū)ο蟮某蓡T變量汇跨,那在第二次標(biāo)記時它將移除出“即將回收”的集合。如果對象這時候還沒逃脫妆距,那基本上它就真的被回收了穷遂。
    見示例程序:
@Data
public class User {
    private int id;
    private String name;
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("關(guān)閉資源! user"+ id + "即將被回收");
    }
}

public class FinalizeTest {
    public static void main(String[] args) {
        List<User> list = new ArrayList<User>();

        int i = 0;
        int j = 0;
        while (true) {
            list.add(new User(i++, UUID.randomUUID().toString()));
            new User(j--, UUID.randomUUID().toString());
        }
    }
}

輸出

...
關(guān)閉資源! user-31945即將被回收
關(guān)閉資源! user-31946即將被回收
關(guān)閉資源! user-31947即將被回收
關(guān)閉資源! user-31948即將被回收
關(guān)閉資源! user-31949即將被回收
關(guān)閉資源! user-31951即將被回收
關(guān)閉資源! user-31952即將被回收
關(guān)閉資源! user-31953即將被回收
...

一旦垃圾回收器準(zhǔn)備好釋放對象占用的存儲空間,將首先調(diào)用其finalize() 方法娱据,并且在下一次垃圾回收動作發(fā)生時蚪黑,才真正回收對象占用的內(nèi)存。

注意:finalize只有垃圾回收GC的時候才會去調(diào)用中剩,但程序不一定會GC忌穿。
finalize未執(zhí)行
public class FinalizeTest {
    public static void main(String[] args) {
        FinalizeTest ft = new FinalizeTest();
    }
    @Override
    protected void finalize() throws Throwable {
        System.out.println("it is finalized!");
    }
}

finalize只有垃圾回收的時候才會去調(diào)用。操作系統(tǒng)結(jié)束一個進(jìn)程结啼,會把那個進(jìn)程申請的內(nèi)存都清了掠剑,所以用不到gc

finalize執(zhí)行
public class FinalizeTest {
    public static void main(String[] args) throws Exception {
        FinalizeTest ft = new FinalizeTest();
        //表示ttf已經(jīng)不可能有任何線程會使用它了
        ft = null;
        //運(yùn)行垃圾回收器
        System.gc();

    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("it is finalized!");
    }
}
jvm在什么情況下會執(zhí)行GC?
  • 對象沒有引用
  • 作用域發(fā)生未捕獲異常
  • 程序在作用域正常執(zhí)行完畢
  • 程序執(zhí)行了System.exit()
  • 程序發(fā)生意外終止(被殺進(jìn)程等)

1.4 如何判斷一個常量是廢棄常量

運(yùn)行時常量池主要回收的是廢棄的常量。那么郊愧,我們?nèi)绾闻袛嘁粋€常量是廢棄常量呢朴译?
假如在常量池中存在字符串 "abc",如果當(dāng)前沒有任何String對象引用該字符串常量的話属铁,就說明常量 "abc" 就是廢棄常量眠寿,如果這時發(fā)生內(nèi)存回收的話而且有必要的話,"abc" 就會被系統(tǒng)清理出常量池红选。

1.5 如何判斷一個類是無用的類

方法區(qū)主要回收的是無用的類澜公,那么如何判斷一個類是無用的類的呢姆另?
判定一個常量是否是“廢棄常量”比較簡單喇肋,而要判定一個類是否是“無用的類”的條件則相對苛刻許多坟乾。類需要同時滿足下面3個條件才能算是 “無用的類” :
該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例蝶防。
加載該類的 ClassLoader 已經(jīng)被回收甚侣。
該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法间学。
虛擬機(jī)可以對滿足上述3個條件的無用類進(jìn)行回收殷费,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收低葫。

2.垃圾收集算法

2.1 標(biāo)記-清除算法

算法分為“標(biāo)記”和“清除”階段:首先標(biāo)記出所有需要回收的對象详羡,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。它是最基礎(chǔ)的收集算法嘿悬,效率也很高实柠,但是會帶來兩個明顯的問題:
效率問題
空間問題(標(biāo)記清除后會產(chǎn)生大量不連續(xù)的碎片)


image.png
2.2 復(fù)制算法

為了解決效率問題,“復(fù)制”收集算法出現(xiàn)了善涨。它可以將內(nèi)存分為大小相同的兩塊窒盐,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后钢拧,就將還存活的對象復(fù)制到另一塊去蟹漓,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收源内。


image.png
2.3 標(biāo)記-整理算法

根據(jù)老年代的特點(diǎn)特出的一種標(biāo)記算法葡粒,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象回收姿锭,而是讓所有存活的對象向一段移動塔鳍,然后直接清理掉端邊界以外的內(nèi)存。


image.png
2.4 分代收集算法

當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法呻此,這種算法沒有什么新的思想轮纫,只是根據(jù)對象存活周期的不同將內(nèi)存分為幾塊。一般將java堆分為新生代和老年代焚鲜,這樣我們就可以根據(jù)各個年代的特點(diǎn)選擇合適的垃圾收集算法掌唾。
比如在新生代中,每次收集都會有大量對象死去忿磅,所以可以選擇復(fù)制算法糯彬,只需要付出少量對象的復(fù)制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的葱她,而且沒有額外的空間對它進(jìn)行分配擔(dān)保撩扒,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吨些,一起剝皮案震驚了整個濱河市搓谆,隨后出現(xiàn)的幾起案子炒辉,更是在濱河造成了極大的恐慌,老刑警劉巖泉手,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黔寇,死亡現(xiàn)場離奇詭異,居然都是意外死亡斩萌,警方通過查閱死者的電腦和手機(jī)缝裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颊郎,“玉大人憋飞,你說我怎么就攤上這事∧房裕” “怎么了搀崭?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猾编。 經(jīng)常有香客問我瘤睹,道長,這世上最難降的妖魔是什么答倡? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任轰传,我火速辦了婚禮,結(jié)果婚禮上瘪撇,老公的妹妹穿的比我還像新娘获茬。我一直安慰自己,他們只是感情好倔既,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布恕曲。 她就那樣靜靜地躺著,像睡著了一般渤涌。 火紅的嫁衣襯著肌膚如雪佩谣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天实蓬,我揣著相機(jī)與錄音茸俭,去河邊找鬼。 笑死安皱,一個胖子當(dāng)著我的面吹牛调鬓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酌伊,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼腾窝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虹脯,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤辜贵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后归形,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鼻由,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年暇榴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕉世。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔼紧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狠轻,到底是詐尸還是另有隱情奸例,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布向楼,位于F島的核電站查吊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏湖蜕。R本人自食惡果不足惜逻卖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昭抒。 院中可真熱鬧评也,春花似錦、人聲如沸灭返。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熙含。三九已至罚缕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怎静,已是汗流浹背怕磨。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留消约,地道東北人肠鲫。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像或粮,于是被迫代替她去往敵國和親导饲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容