GC-談談“生死”

引言:

今天週六,技術博客不會斷更。今天想和大家聊聊“生死”。有研讀過jvm的小夥伴們知道java相比於c++來說,內存動態分配垃圾回收技術是兩大核心。一個對象的出生由我們來創造,但是對象的死亡很多時候並不由我們決定,而是由垃圾回收技術進行管理和操作。在“技術點”我會簡單介紹一下jvm的組成結構和它們與線程的關係,方便我們更好理解。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290

技術點:

1、jvm運行時數據區組成結構

方法區、虛擬機棧、本地方法棧、堆和程序計數器。下面是我從百度找的圖片:

這裏寫圖片描述

對於每一個模塊的作用,筆者就不再展開了,如果需要的話,我會在後面可以專門挑一天寫一個博文,專門介紹每個部分的作用和運行狀態。話雖如此,知識本來就是比較發散的東西,我就說幾點我面試過程中遇到最多問題:①對象實例存在哪?存在堆中。②對象引用存在哪啊?存在虛擬機棧(注意:我們平時說的棧就是虛擬機棧)。③已加載的常量,靜態變量,編譯後的代碼存在哪啊?存在方法區。ok,點到即止,如果大家很想深入瞭解這塊的話,可以聯繫我。迴歸正題,從上圖中可以看出:方法區和堆是線程共享的,而虛擬機棧,本地方法棧和程序計數器是線程隔離的。

2、引用的概念

很早以前的java中,引用的定義是:虛擬機棧中的本地變量表中存儲的數值代表的是另一塊內存的地址,就稱另一塊塊內存被引用。但是現在區分遠比以前要嚴謹的多,主要分爲以下四種引用類型:①強引用;②軟引用;③弱引用;④虛引用。強引用就是永遠不會回收掉被引用的對象,比如說我們代碼中new出來的對象。軟引用表示有用但是非必需的,如果系統內存資源緊張,可能就會被回收;弱引用表示非必需的對象,只能存活到下一次垃圾回收發生之前;虛引用是最弱的,這個引用無法操作對象。

如果引用不存在了,就表示對象死亡了。

控制哪裏的“生死”?

從技術點1我們可以看出,虛擬機棧,程序計數器和本地方法棧都是線程隔離的,隨着線程而生,隨着線程而死,考慮它的死活並沒有什麼意義。而堆和方法區是線程共享的,程序運行之後,我們才知道創建了多少對象,所以在這部分中內存是動態分配的,所以這部分纔是“生死”的關鍵區域。

如何判斷“生死”?

gc機制作爲對象的“生死”的“收屍者”,在工作之前需要先準確判斷好某個對象是否還存活,不然就會出大事了。在這裏主要會有兩種常見的辦法進行判斷:

1、引用計數法:每個對象都會被配備一個引用計數器,每當有一個地方引用它時,計數器就+1;引用失效時,計數器就-1。當引用計數器爲0的時候就表示對象已經死亡。但是這個辦法有一個很嚴重的bug,如果兩個對象相互引用咋辦?假如兩個對象之存在相互引用,除此之外沒有其餘的引用,那麼這兩個對象已經死亡了。如果用引用計數法就會無法回收。所以目前jdk版本是不會使用這種方式判斷。

2、可達性分析算法:找尋特定點(GC Roots)作爲根節點(一般來說不止存在一個根節點的),從這些節點的引用關係向下搜索,會組成一條鏈子一樣的東西,這個東西我們稱爲引用鏈。如果某一個對象不存在任何一條引用鏈上就表示這個對象已經死亡了。

置於死地而後生

被判定死亡的對象真的就非死不可了麼?其實不然的。這裏爲在說一個面試經常會問到的問題,用以引出爲什麼會置於死地而後生。請簡述:final,finally,finalize的區別?final是java關鍵字,如果用於修飾類,表示這個類不可以被繼承,比如說String類。如果用於修飾變量,那麼變量在使用過程中就不能被修改,比如我們項目中都會有一個commconstent類吧。finally是存在於try-catch結構之中,表示不管try-catch怎麼運行都會執行finally中的代碼。

好啦,finalize就是我們今天要說的能置對象死地而後生的東西。如果在可達性分析算法過程中,發現某一個對象與引用鏈並不關聯,那麼他就會去判斷這個對象中有沒有覆蓋finalize()方法,如果覆蓋了該方法,該對象就會放置到一個F-Queue的隊列之中。後面就會執行它(執行並不保證會執行完,因爲finalize方法過於繁瑣或者執行中出了錯誤,爲了不讓隊列中其他對象處於永久等待會中止執行)。

也就是說finalize()方法是對象的最後一根“救命稻草”,也是“後生”的關鍵,錯過這個機會,基本上就拜拜了。而且,更寧這個生死邊緣的對象難過的是,如果靠finalize()方法救活一次,第二次又要進入回收了,finalize()方法就不會再次救它了。這裏還要說明一下,對象“救活”成功需要在finalize()方法中,把自己關聯到引用鏈上,關聯不上也是要面臨死亡回收。所以,finalize()方法在日常開發中能不用盡量別用,很容易就脫離你的掌控。

下面就是finalize()方法就活對象的demo:

package com.bw;

public class GcTest {

    public static GcTest gcTest = null;

    @Override
    protected void finalize() throws Throwable {//重寫finalize方法
        super.finalize();
        System.out.println("執行finalize方法");
        GcTest.gcTest = this;//把自己關聯到引用鏈上
    }

    public static void main(String[] args) throws InterruptedException {
        gcTest = new GcTest();//創建一個悲劇對象

        //第一次嘗試救活
        gcTest = null;
        System.gc();
        Thread.sleep(500);//finalize()方法優先級很低,需要等待一下它
        if(gcTest != null){
            System.out.println("第一次被救活");
        }else {
            System.out.println("第一次救活失敗");
        }

        //對象救活後又失去了和引用鏈到關聯,也就是進入第二次嘗試救活

        gcTest = null;
        System.gc();
        Thread.sleep(500);
        if(gcTest != null){
            System.out.println("第二次被救活");
        }else {
            System.out.println("第二次救活失敗");
        }

    }
}


//輸出:執行finalize方法
        第一次被救活
       第二次救活失敗

從demo中可以發現,第二次的時候,finalize救都懶的救了。所以呀,人生中也一樣,很多時候,機會對於你來說,可能也只能存在一次,所以好好珍惜生活中的每一次機遇。

如果博文存在什麼問題,或者有什麼想法,可以聯繫我呀,下面是我的微信二維碼:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章