【Java】深入理解Java虛擬機(jī)2——判斷對象是否存活和引用

為什么我們還要去了解GC和內(nèi)存分配呢轨奄?答案很簡單:當(dāng)需要排查各種內(nèi)存溢出彤恶、內(nèi)存泄漏問題時,當(dāng)垃圾收集成為系統(tǒng)達(dá)到更高并發(fā)量的瓶頸時越驻,我們就需要對這些“自動化”的技術(shù)實施必要的監(jiān)控和調(diào)節(jié)叽掘。

其他文章

【Java】深入理解Java虛擬機(jī)1——內(nèi)存區(qū)域以及OOM類型:http://www.reibang.com/p/65c91ba4006e
【Java】深入理解Java虛擬機(jī)2——判斷對象是否存活和引用:http://www.reibang.com/p/67c24aa93c03
【Java】深入理解Java虛擬機(jī)3——垃圾收集算法:http://www.reibang.com/p/362407886236
【Java】深入理解Java虛擬機(jī)4——內(nèi)存分配與回收策略:http://www.reibang.com/p/e21f5d5c4f42
【Java】深入理解Java虛擬機(jī)5——類的加載過程:http://www.reibang.com/p/931ef115d48e
【Java】深入理解Java虛擬機(jī)6——類的加載器及雙親委派:http://www.reibang.com/p/2f33eca93a4f

垃圾回收針對區(qū)域

Java內(nèi)存運(yùn)行時區(qū)域的各個部分楣铁,其中程序計數(shù)器、虛擬機(jī)棧更扁、本地方法棧3個區(qū)域隨線程而生盖腕,隨線程而滅;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作浓镜。每一個 棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來時就已知的(盡管在運(yùn)行期會由JIT編譯器進(jìn)行一些優(yōu)化溃列,但在本章基于概念模型的討論中,大體上可以認(rèn)為是編譯期可知的)膛薛,因此這幾個區(qū)域的內(nèi)存分配和回收都具備確定性哭廉,在這幾個區(qū)域內(nèi)就不需要過多考慮回收的問題,因為方法結(jié)束或者線程結(jié)束時相叁,內(nèi)存自然就跟隨著回收了。而Java堆和方法區(qū)則不一 樣辽幌,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣增淹,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間時才能知道會創(chuàng)建哪些對象乌企,這部分內(nèi)存的分配和回收都是動態(tài)的虑润,垃圾收集器所關(guān)注的是這部分內(nèi)存,本文后續(xù)討論中的“內(nèi)存”分配與回收也僅指這一部分內(nèi)存加酵。

判斷對象是否存活

1.引用計數(shù)算法

給對象中添加一個引用計數(shù)器拳喻,每當(dāng)有一個地方引用它時哭当,計數(shù)器值就加1;當(dāng)引用失效時冗澈,計數(shù)器值就減1钦勘;任何時刻計數(shù)器為0的對象就是不可能再被使用的。
主流的Java虛擬機(jī)里面沒有選用引用計數(shù)算法來管理內(nèi)存亚亲,其中最主要的原因是它很難解決對象之間相互循環(huán)引用的問題彻采。 下面舉個例子:
對象objA和objB都有字段instance,賦值令objA.instance=objB及objB.instance=objA捌归,除此之外肛响,這兩個對象再無任何引用,實際上這兩個對象已經(jīng)不可能再被訪問惜索,但是它們因為互相引用著對方特笋,導(dǎo)致它們的引用計數(shù)都不為0,于是引用計數(shù)算法無法通知GC收集器回收它們巾兆。

    /**
     * testGC()方法執(zhí)行后猎物,objA和objB會不會被GC呢? *@author zzm
     */
    public class ReferenceCountingGC {
        public Object instance = null臼寄;private static final int_1MB=1024*1024霸奕;
        /**
         * 這個成員屬性的唯一意義就是占點(diǎn)內(nèi)存,以便能在GC日志中看清楚是否被回收過
         */
        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煤惩;objA = null;
            objB = null炼邀; //假設(shè)在這行發(fā)生GC,objA和objB是否能被回收魄揉? System.gc(); } }
        }
    }

運(yùn)行結(jié)果:
image.png

從運(yùn)行結(jié)果中可以清楚看到拭宁,GC日志中包含“4603K->210K”洛退,意味著虛擬機(jī)并沒有因為這兩個對象互相引用就不回收它們,這也從側(cè)面說明虛擬機(jī)并不是通過引用計數(shù)算法來判 斷對象是否存活的杰标。

2.可達(dá)性分析算法

在主流的商用程序語言(Java兵怯、C#,甚至包括前面提到的古老的Lisp)的主流實現(xiàn)中腔剂,都是稱通過可達(dá)性分析(Reachability Analysis)來判定對象是否存活的媒区。算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)袜漩,當(dāng)一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說绪爸,就是從GC Roots到這個對象不可達(dá))時,則證明此對象是不可用的宙攻。如圖:奠货,對象object 5、object 6粘优、object 7雖然互相有關(guān)聯(lián)仇味,但是它們到GC Roots是不可達(dá)的,所以它們將會被判定為是可回收的對象雹顺。
可達(dá)性分析算法.png

在Java語言中丹墨,可作為GC Roots的對象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象。
  • 方法區(qū)中類靜態(tài)屬性引用的對象嬉愧。
  • 方法區(qū)中常量引用的對象贩挣。
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象。

再談引用

無論是通過引用計數(shù)算法判斷對象的引用數(shù)量没酣,還是通過可達(dá)性分析算法判斷對象的引用鏈?zhǔn)欠窨蛇_(dá)王财,判定對象是否存活都與“引用”有關(guān)。 我們希望能描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時裕便,則能保留在內(nèi)存之中绒净;如果內(nèi)存空間在進(jìn)行垃圾收集后還是非常緊張,則可以拋棄這些對象偿衰。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景挂疆。在JDK 1.2之后,Java對引用的概念進(jìn)行了擴(kuò)充下翎,將引用分為強(qiáng)引用(Strong Reference)缤言、軟引用(Soft Reference)、弱引用(Weak Reference)视事、虛引用(PhantomReference)4種胆萧,這4種引用強(qiáng)度依次逐漸減弱。
強(qiáng)引用就是指在程序代碼之中普遍存在的俐东,類似“Object obj=new Object()”這類的引用跌穗,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象虏辫。比如你創(chuàng)建一個很長的數(shù)組Object[] objArr = new Object[10000];當(dāng)運(yùn)行至這句時瞻离,如果內(nèi)存不足,JVM會拋出OOM錯誤也不會回收數(shù)組中的object對象乒裆。不過要注意的是,當(dāng)方法運(yùn)行完之后,數(shù)組和數(shù)組中的對象都已經(jīng)不存在了鹤耍,所以它們指向的對象都會被JVM回收肉迫。
軟引用是用來描述一些還有用但并非必需的對象。對于軟引用關(guān)聯(lián)著的對象稿黄,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前喊衫,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存杆怕,才會拋出內(nèi)存溢出異常族购。在JDK 1.2之后,提供了SoftReference類來實現(xiàn)軟引用陵珍。一般可用來實現(xiàn)緩存寝杖,在使用之前需要判空,從而判斷當(dāng)前時候已經(jīng)被回收了互纯。
弱引用也是用來描述非必需對象的瑟幕,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前留潦。當(dāng)垃圾收集器工作時只盹,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象兔院。在JDK 1.2之后殖卑,通過java.lang.ref.WeakReference或java.util.WeakHashMap類實現(xiàn),eg : WeakReference p = new WeakReference(new Person("Rain"));不管內(nèi)存是否足夠坊萝,系統(tǒng)垃圾回收時必定會回收孵稽。
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系屹堰。一個對象是否有虛引用的存在肛冶,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例扯键。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知睦袖。在JDK 1.2之后,提供了PhantomReference類來實現(xiàn)虛引用荣刑。不能單獨(dú)使用馅笙,主要是用于追蹤對象被垃圾回收的狀態(tài)。通過java.lang.ref.PhantomReference類和引用隊列ReferenceQueue類聯(lián)合使用實現(xiàn)厉亏。

finalize()

即使在可達(dá)性分析算法中不可達(dá)的對象董习,也并非是“非死不可”的,這時候它們暫時處 于“緩刑”階段爱只,要真正宣告一個對象死亡皿淋,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行可達(dá) 性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標(biāo)記并且進(jìn)行一次篩選, 篩選的條件是此對象是否有必要執(zhí)行finalize()方法窝趣。當(dāng)對象沒有覆蓋finalize()方法疯暑,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”哑舒。

/**
     * 此代碼演示了兩點(diǎn): *1.對象可以在被GC時自我拯救妇拯。 *2.這種自救的機(jī)會只有一次,因為一個對象的finalize()方法最多只會被系統(tǒng)自動調(diào)用一次 *@author zzm
     */
    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 mehtod executed洗鸵!");
            FinalizeEscapeGC.SAVE_HOOK = this;
        }

        public static void main(String[] args) throws Throwable {
            SAVE_HOOK = new FinalizeEscapeGC();
            //對象第一次成功拯救自己
            SAVE_HOOK = null;
            System.gc();
            //因為finalize方法優(yōu)先級很低越锈,所以暫停0.5秒以等待它 
            Thread.sleep(500);
            if (SAVE_HOOK!=null){
                SAVE_HOOK.isAlive();
            }else{
                System.out.println("no,i am dead:(");
            } //下面這段代碼與上面的完全相同膘滨,但是這次自救卻失敗了 
            SAVE_HOOK = null;
            System.gc();
            //因為finalize方法優(yōu)先級很低甘凭,所以暫停0.5秒以等待它 
            Thread.sleep(500);
            if (SAVE_HOOK!=null){
                SAVE_HOOK.isAlive();
            }else{
                System.out.println("no,i am dead:(");
            }
        }
    }
  運(yùn)行結(jié)果:
    finalize mehtod executed吏祸!
    yes,i am still alive:)
    no,i am dead:(

一個值得注意的地方是对蒲,代碼中有兩段完全一樣的代碼片段,執(zhí)行結(jié)果卻是一次逃脫成功贡翘,一次失敗蹈矮,這是因為任何一個對象的finalize()方法都只會被系統(tǒng)自動調(diào)用一次, 如果對象面臨下一次回收鸣驱,它的finalize()方法不會被再次執(zhí)行泛鸟,因此第二段代碼的自救行動失敗了。
筆者建議大家盡量避免使用它踊东,它的運(yùn)行代價高昂北滥,不確定性大,無法保證各個對象的調(diào)用順序闸翅。有些教材中描述它適合做“關(guān)閉外部資源”之類的工作再芋,這完全是對這個方法用途的一種自我安慰。 finalize()能做的所有工作坚冀,使用try-finally或者其他方式都可以做得更好济赎、更及時。

錯誤不足之處或相關(guān)建議歡迎大家評論指出记某,謝謝司训!如果覺得內(nèi)容可以的話麻煩喜歡(?)一下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市液南,隨后出現(xiàn)的幾起案子壳猜,更是在濱河造成了極大的恐慌,老刑警劉巖滑凉,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件统扳,死亡現(xiàn)場離奇詭異喘帚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)闪幽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門啥辨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盯腌,你說我怎么就攤上這事≡纱瘢” “怎么了腕够?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舌劳。 經(jīng)常有香客問我帚湘,道長,這世上最難降的妖魔是什么甚淡? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任大诸,我火速辦了婚禮,結(jié)果婚禮上贯卦,老公的妹妹穿的比我還像新娘资柔。我一直安慰自己,他們只是感情好撵割,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布贿堰。 她就那樣靜靜地躺著,像睡著了一般啡彬。 火紅的嫁衣襯著肌膚如雪羹与。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天庶灿,我揣著相機(jī)與錄音纵搁,去河邊找鬼。 笑死往踢,一個胖子當(dāng)著我的面吹牛腾誉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菲语,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼妄辩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了山上?” 一聲冷哼從身側(cè)響起眼耀,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎佩憾,沒想到半個月后哮伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體干花,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年楞黄,在試婚紗的時候發(fā)現(xiàn)自己被綠了池凄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡鬼廓,死狀恐怖肿仑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碎税,我是刑警寧澤尤慰,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站雷蹂,受9級特大地震影響伟端,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匪煌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一责蝠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萎庭,春花似錦霜医、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至达舒,卻和暖如春值朋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巩搏。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工昨登, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贯底。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓丰辣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親禽捆。 傳聞我的和親對象是個殘疾皇子笙什,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355