對(duì)象是"存活"還是"已死

對(duì)象是"存活"還是"已死"

在堆里面存放著Java世界中幾乎所有的對(duì)象實(shí)例泪幌,垃圾收集器在對(duì)堆進(jìn)行回收前肴焊。第一件事情就是確定這些對(duì)象之中哪些還"存活"其屏,哪些是"已死"(即不可能在
被任何途徑使用的對(duì)象)

  • 引用計(jì)數(shù)法
  • 可達(dá)性分析算法
  • 生存還是死亡
  • 回收方法區(qū)

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

很多教科書判斷對(duì)象是否存活的算法是這樣的:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)措译,計(jì)數(shù)器就加1别凤;當(dāng)引用失效時(shí),計(jì)數(shù)器就減1领虹;任何時(shí)刻
計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的规哪。</br>
客觀地說,引用計(jì)數(shù)算法的實(shí)現(xiàn)簡(jiǎn)單塌衰,判斷效率很高诉稍,在大部分情況下它都是一個(gè)不錯(cuò)的算法,也有一些比較著名的應(yīng)用案例最疆。但是杯巨,至少主流的Java虛擬機(jī)里
面沒有使用引用計(jì)數(shù)算法來管理內(nèi)存,其中最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問題努酸。</br>
如下測(cè)試代碼:Test0.class

/**
 * * * * 社
 * * * * * 會(huì)
 * * * * * * 主義
 * * * * * 碼
 * * * * 農(nóng)
 * VM Args: -Xmx10m -Xms10m -XX:+PrintGCDetails -XX:+UseParNewGC
 * @author FeiYun
 **/
public class Test1 {

    public Object instance = null;

    private static final int ONE_MB = 1024 * 1024;

    //為了占內(nèi)存
    private byte[] bigSize = new byte[2 * ONE_MB];

    public static void testGC() {
        Test1 objA = new Test1();
        Test1 objB = new Test1();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        //在這里進(jìn)行GC服爷,看objA和objB是否會(huì)被回收
        System.gc();
    }

    public static void main(String[] args) {
        Test1.testGC();
    }

}

輸出結(jié)果:

[Full GC (System.gc()) [Tenured: 2554K->467K(6848K), 0.0014870 secs] 4729K->467K(9920K), [Metaspace: 2988K->2988K(1056768K)], 0.0015045 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 3072K, used 97K [0x00000007bf600000, 0x00000007bf950000, 0x00000007bf950000)
  eden space 2752K,   3% used [0x00000007bf600000, 0x00000007bf6185d8, 0x00000007bf8b0000)
  from space 320K,   0% used [0x00000007bf8b0000, 0x00000007bf8b0000, 0x00000007bf900000)
  to   space 320K,   0% used [0x00000007bf900000, 0x00000007bf900000, 0x00000007bf950000)
 tenured generation   total 6848K, used 467K [0x00000007bf950000, 0x00000007c0000000, 0x00000007c0000000)
   the space 6848K,   6% used [0x00000007bf950000, 0x00000007bf9c4d08, 0x00000007bf9c4e00, 0x00000007c0000000)
 Metaspace       used 3004K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 328K, capacity 388K, committed 512K, reserved 1048576K

從運(yùn)行結(jié)果中可以清楚看到,GC日志中包含"4729K->467K",意味著虛擬機(jī)并沒有因?yàn)檫@兩個(gè)對(duì)象互相引用就不回收它們层扶,這也從側(cè)面說明虛擬機(jī)并不是通過引
用計(jì)數(shù)算法來判斷對(duì)象是否存活的。


可達(dá)性分析算法

在主流的商用程序語言的主流實(shí)現(xiàn)中烙荷,都是稱通過可達(dá)性分析(Reachability Analysis)來判定對(duì)象是否存活的镜会。這個(gè)算法的基本思路就是通過一系列的稱
為"GC Roots"的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始鄉(xiāng)下搜索终抽,搜索所走過的路徑稱為引用鏈(Reference Chain)戳表,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈
相連時(shí),則證明此對(duì)象是不可用的昼伴。
<img src="https://thumbnail1.baidupcs.com/thumbnail/7e811edd4od7b825869a052cb9b084a2?fid=1080334218-250528-592287742302570&rt=pr&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-s6YILUzhndggiR0kQGh05ZAad7g%3d&expires=8h&chkbd=0&chkv=0&dp-logid=31134765181005687&dp-callid=0&time=1666850400&size=c1920_u1080&quality=90&vuk=1080334218&ft=image&autopolicy=1">

在Java語言中匾旭,可作為GC Roots的對(duì)象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量)中引用的對(duì)象。
  • 方法區(qū)中靜態(tài)屬性引用的對(duì)象圃郊。
  • 方法區(qū)中常量引用的對(duì)象价涝。
  • 本地方法棧中JNI(即一般說的Native方法)引用的對(duì)象。

再談引用

無論是通過引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量持舆,還是通過可達(dá)性分析算法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)色瘩,判斷對(duì)象是否存活都與"引用"有關(guān)。在JDK1.2以前逸寓,Java
中的引用的定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址居兆,就稱這塊內(nèi)存代表著一個(gè)引用。這種定義很純粹竹伸,但是太
過狹隘泥栖,一個(gè)對(duì)象在這種定義下只有被引用或者沒有被引用兩種狀態(tài),對(duì)于如何描述一些"食之無味棄之可惜"的對(duì)象就顯得無能為力勋篓。</br>
在JDK1.2之后吧享,Java對(duì)引用的概念進(jìn)行了補(bǔ)充,將引用分為強(qiáng)引用(Strong Reference)譬嚣、軟引用(Soft Reference)耙蔑、弱引用(Weak Reference)、
虛引用(Phantom Reference)4種孤荣,這四種引用強(qiáng)度依次逐漸減弱甸陌。

  • 強(qiáng)引用就是指在程序代碼之中普遍存在的,類似"Object obj = new Object()"這類的引用盐股,只要強(qiáng)引用還存在钱豁,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的
    對(duì)象
  • 軟引用是用來描述一些還有用但并非必須的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象疯汁,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前牲尺,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第
    二次回收男杈,如果這次回收沒有足夠的內(nèi)存越妈,才會(huì)拋出內(nèi)存溢出異常。在JDK1.2之后,提供了SoftReference類來實(shí)現(xiàn)軟引用竞漾。
  • 弱引用也是用來描述非必需對(duì)象的,但是它的強(qiáng)度比軟引用更弱一些休溶,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前府阀。當(dāng)垃圾收集器工作時(shí),無
    論當(dāng)前內(nèi)存是否足夠搓茬,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象犹赖。在JDK1.2之后,提供了WeakReference類來實(shí)現(xiàn)弱引用卷仑。
  • 虛引用也被稱為幽靈引用或者幻影引用峻村,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象是否有虛引用的存在锡凝,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響粘昨,也無法通過虛引用來
    取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收的時(shí)候收到一個(gè)系統(tǒng)通知窜锯。在JDK1.2之后雾棺,提供了PhantomReference
    類來實(shí)現(xiàn)虛引用。

生存還是死亡

即使在可達(dá)性分析算法中不可達(dá)的對(duì)象衬浑,也并非"非死不可"的捌浩,這時(shí)候它們暫時(shí)處于"緩行"階段,要真正宣告一個(gè)對(duì)象死亡工秩,至少要經(jīng)歷兩次標(biāo)記過程:

  • 第一次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈尸饺,那它會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選。篩選的條件是此對(duì)象是否
    有必要執(zhí)行finalize()方法助币。當(dāng)對(duì)象沒有覆蓋finalize()方法浪听,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為"沒有必要執(zhí)行"
    如果對(duì)象被判斷定位有必要執(zhí)行finalize()方法眉菱,那個(gè)這個(gè)對(duì)象會(huì)被放置在一個(gè)叫做F-Queue的隊(duì)列中迹栓,并在稍后有一個(gè)虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的Finalizer
    線程去執(zhí)行它俭缓。這里所謂的"執(zhí)行"是指迅即會(huì)觸發(fā)這個(gè)方法克伊,但并不會(huì)承諾會(huì)等待它運(yùn)行結(jié)束,這個(gè)樣做的原因是华坦,如果一個(gè)對(duì)象在finalize()方法中執(zhí)行
    緩慢愿吹,或者發(fā)生死循環(huán),將可能會(huì)導(dǎo)致F-Queue隊(duì)列中其他對(duì)象永久處于等待惜姐,甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰犁跪。finalize()方法是對(duì)象逃脫死亡命運(yùn)的最
    后一次機(jī)會(huì)椿息,稍后GC會(huì)對(duì)F-Queue中的對(duì)象進(jìn)行二次標(biāo)記。
  • 第二次標(biāo)記過程:如果對(duì)象在finalize()中成功拯救自己——只要重新與引用鏈上的任何對(duì)象建立關(guān)聯(lián)即可坷衍,譬如把自己(this關(guān)鍵字)賦值給某個(gè)類變量
    或者對(duì)象的成員變量寝优,那在第二次標(biāo)記時(shí)它將被移除出"即將回收"的集合;如果對(duì)象這個(gè)時(shí)候還沒有逃脫死亡的命運(yùn)枫耳,那基本上它就真的被回收了乏矾。

如下測(cè)試代碼:Test2.class

/**
 * * * * 社
 * * * * * 會(huì)
 * * * * * * 主義
 * * * * * 碼
 * * * * 農(nóng)
 * 1、對(duì)象可以在GC時(shí)被自救
 * 2嘉涌、自救只能有一次妻熊,因?yàn)閷?duì)對(duì)象的finalizer()方法只會(huì)被系統(tǒng)調(diào)用一次
 * @author FeiYun
 **/
public class Test2 {

    public static Test2 instance = null;

    public void isAlive() {
        System.out.println("yes I am still alive:");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed夸浅!");
        Test2.instance = this;
    }

    public static void main(String[] args) throws Throwable {
        instance = new Test2();

        //對(duì)象第一次成功拯救自己
        instance = null;
        System.gc();
        //因?yàn)閒inalizer()方法執(zhí)行優(yōu)先級(jí)很低仑最,所以等待0.5秒
        Thread.sleep(500);

        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("no I am dead :(");
        }

        //嘗試對(duì)象第二次成功拯救自己
        instance = null;
        System.gc();

        Thread.sleep(500);

        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("no I am dead :(");
        }
    }
}

輸出結(jié)果:

finalize method executed!
yes I am still alive:
no I am dead :(

從結(jié)果可以看出帆喇,instance對(duì)象的finalizer()方法確實(shí)被GC收集器觸發(fā)過警医,并且在被收集前成功逃脫了,但是需要說明的是finalizer()方法只會(huì)被系統(tǒng)自
動(dòng)調(diào)用一次坯钦,如果對(duì)象面臨下一次回收预皇,它的finalizer()方法不會(huì)再被執(zhí)行,因此第二段代碼的自救行動(dòng)失敗了婉刀。


回收方法區(qū)

永久代的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類吟温。

  • 廢棄常量

回收廢棄常量與回收J(rèn)ava堆中的對(duì)象非常類似。以常量池中字面量的回收為例突颊,假如一個(gè)字符串"abc"已經(jīng)進(jìn)入了常量池中鲁豪,但是當(dāng)前系統(tǒng)沒有任何一個(gè)
String對(duì)象叫做"abc"的,換句話說律秃,就是沒有任何String對(duì)象引用常量池中的"abc"常量爬橡,也沒有其他地方引用這個(gè)字面量,如果這時(shí)發(fā)生內(nèi)存回收棒动,而
且必要的話糙申,這個(gè)"abc"常量就會(huì)被系統(tǒng)清理出常量池。常量池中的其他類(接口)船惨、方法柜裸、字段的符號(hào)引用也與此類似。

  • 廢棄無用的類

判斷一個(gè)常量是否是"廢棄常量"比較簡(jiǎn)單粱锐,而要判定是個(gè)類是否是"無用的類"的條件則相對(duì)苛刻許多粘室。類需要同時(shí)滿足下面三個(gè)條件才能算是"無用的類":

  • 該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該累的任何實(shí)例卜范。
  • 加載類的ClassLoader已經(jīng)被回收衔统。
  • 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

虛擬機(jī)可以對(duì)滿足上述3個(gè)條件的無用類進(jìn)行回收锦爵,這里說的僅僅是"可以"舱殿,而并不是和對(duì)象一樣,不使用了就必然回收险掀。是否對(duì)類進(jìn)行回收沪袭,HotSpot虛擬機(jī)
提供了-Xnoclassgc參數(shù)進(jìn)行控制,還可以使用-verbose:class以及-XX:+TraceClassLoading樟氢、-XX:+TraceClassUnLoading查看類加載和卸載信息冈绊,
其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虛擬機(jī)中使用,-XX:+TraceClassUnLoading參數(shù)需要FastDebug版的虛擬機(jī)埠啃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末死宣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碴开,更是在濱河造成了極大的恐慌毅该,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潦牛,死亡現(xiàn)場(chǎng)離奇詭異眶掌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巴碗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門朴爬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橡淆,你說我怎么就攤上這事召噩。” “怎么了明垢?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蚣常,是天一觀的道長。 經(jīng)常有香客問我痊银,道長抵蚊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任溯革,我火速辦了婚禮贞绳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘致稀。我一直安慰自己冈闭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布抖单。 她就那樣靜靜地躺著萎攒,像睡著了一般遇八。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耍休,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天刃永,我揣著相機(jī)與錄音,去河邊找鬼羊精。 笑死斯够,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喧锦。 我是一名探鬼主播读规,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼燃少!你這毒婦竟也來了束亏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤供汛,失蹤者是張志新(化名)和其女友劉穎枪汪,沒想到半個(gè)月后涌穆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怔昨,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年宿稀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趁舀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祝沸,死狀恐怖矮烹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罩锐,我是刑警寧澤奉狈,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站涩惑,受9級(jí)特大地震影響仁期,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竭恬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一跛蛋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痊硕,春花似錦赊级、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橡伞。三九已至,卻和暖如春晋被,著一層夾襖步出監(jiān)牢的瞬間骑歹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工墨微, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留道媚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓翘县,卻偏偏與公主長得像最域,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锈麸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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