JVM學習(6)jvm如何判斷垃圾

一.概述:

垃圾回收的方法是判斷沒有個對象的可觸及性瞻凤,即從根節(jié)點開始是否可以訪問到這個對象世杀。如果訪問到了,說明當前對象正在使用瞻坝,如果從所以根節(jié)點都無法訪問到某個對象,說明這個對象不再使用了所刀。一般情況下,此對象要被回收浮创。但是,一個無法觸及的對象有可能在某一個條件下復活自己溜族,那么對它回收就不合理了。因此斩祭,需要給出一個對象可觸及性狀態(tài)的定義乡话,并規(guī)定在什么狀態(tài)下,才可以安全回收對象绑青。
可觸及性包括三種:
1.可觸及的:從根節(jié)點可以觸及到這個對象。
2.可復活的:對象所有引用被釋放闸婴,但是在finalize()中可能復活該對象。
3.不可觸及的:對象的finalize()被調(diào)用后降狠,并且沒有被復活,那么就進入不可觸及狀態(tài)榜配,不可觸及的對象不可能被復活吕晌,因為finalize()函數(shù)只會被調(diào)用一次蛋褥。
只有不可觸及的對象可以回收睛驳。

如何確定根節(jié)點:

  • 棧中引用的對象:線程棧中函數(shù)引用的局部變量。
  • 方法區(qū)中靜態(tài)成員或者常量引用的對象(全局對象)
  • JNI方法棧中引用對象

二.對象的復活:

當對象沒有被引用的時候淫茵,這時如果gc,如何不被gc回收呢痘昌?如下代碼:

/**
 * 對象的復活:
 * 1.System.gc()之前會執(zhí)行對象的finalize()方法炬转。
 * 2.對象的finalize()方法讓obj又復活了辆苔。
 * 3.但是一個對象的finalize()方法只能執(zhí)行一次扼劈,所以第二次不能復活。
 * 4.System.gc()只是開始回收荐吵,調(diào)用了finalize()后才是真正意義的回收赊瞬。
 *
 * 如果沒有"obj=null;//**第二次贼涩,不可復活  "這個語句,obj就不可能被回收了呢遥倦?不見得!K跎浮堡称!很危險**
 * 經(jīng)驗:
 * 1.避免使用finalize().
 * 2.優(yōu)先級低瞎抛,何時調(diào)用不確定却紧。(什么時候gc,程序無法確定,系統(tǒng)決定)
 * 3.可以用try-catch-finally來替代它啄寡。
 * Created by chenyang on 2017/2/2.
 *
 */
public class CanReliveObj {
    public static CanReliveObj obj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("CanReliveObj finalize called");
        obj=this;
    }

    @Override
    public String toString() {
        return "I am CanReliveObj";
    }

    public static void main(String[] args) throws InterruptedException{
        obj=new CanReliveObj();
        obj=null;//引用清空,可復活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj 是 null");
        }else {
            System.out.println("obj可用");
        }
        System.out.println("第2次gc");
        obj=null;//不可復活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
             System.out.println("obj是null");
        }else {
            System.out.println("obj可用");
        }
    }
}

如上面的代碼懒浮,finalize()的危險识藤,因為如果沒有"obj=null;第二次砚著,不可復活 "這個語句痴昧,obj就不可能被回收了呢?不見得8献!餐胀!很危險

finalize()淺析:

在說明finalize()的用法之前要樹立有關(guān)于java垃圾回收器幾個觀點:

  • "對象可以不被垃圾回收" : java的垃圾回收遵循一個特點, 就是能不回收就不會回收.只要程序的內(nèi)存沒有達到即將用完的地步, 對象占用的空間就不會被釋放.因為如果程序正常結(jié)束了,而且垃圾回收器沒有釋放申請的內(nèi)存, 那么隨著程序的正常退出, 申請的內(nèi)存會自動交還給操作系統(tǒng); 而且垃圾回收本身就需要付出代價, 是有一定開銷的, 如果不使用,就不會存在這一部分的開銷.
  • 垃圾回收只能回收內(nèi)存, 而且只能回收內(nèi)存中由java創(chuàng)建對象方式(堆)創(chuàng)建的對象所占用的那一部分內(nèi)存, 無法回收其他資源, 比如文件操作的句柄, 數(shù)據(jù)庫的連接等等.
  • 垃圾回收不是C++中的析構(gòu). 兩者不是對應關(guān)系, 因為第一點就指出了垃圾回收的發(fā)生是不確定的, 而C++中析構(gòu)函數(shù)是由程序員控制(delete) 或者離開器作用域時自動調(diào)用發(fā)生, 是在確定的時間對對象進行銷毀并釋放其所占用的內(nèi)存.
  • 調(diào)用垃圾回收器(GC)不一定保證垃圾回收器的運行
  • finalize()的功能 : 一旦垃圾回收器準備釋放對象所占的內(nèi)存空間, 如果對象覆蓋了finalize()并且函數(shù)體內(nèi)不能是空的, 就會首先調(diào)用對象的finalize(), 然后在下一次垃圾回收動作發(fā)生的時候真正收回對象所占的空間.
  • finalize()有一個特點就是: JVM始終只調(diào)用一次. 無論這個對象被垃圾回收器標記為什么狀態(tài), finalize()始終只調(diào)用一次. 但是程序員在代碼中主動調(diào)用的不記錄在這之內(nèi).

經(jīng)驗:

  • 避免使用finalize()瘤载,操作不慎可能導致錯誤。
  • 優(yōu)先級低鸣奔,何時被調(diào)用惩阶, 不確定扣汪。何時發(fā)生GC不確定
  • 可以使用try-catch-finally來替代它。

三.引用的類型和可觸及性的強度:

java提供了4個級別的引用:強引用崭别,軟引用,弱引用和虛引用紊遵。除了強引用外侥蒙,其他3種引用均可以在java.lang.ref包中找到。如下顯示了三種引用對應的類学搜。


image.png

強引用--指向的對象不會被回收

強引用就是程序中一般使用的引用類型论衍,強引用的對象時可觸及的瑞佩,不會被回收坯台。但是,軟引用稠炬,弱引用和虛引用的對象是軟可觸及,弱可觸及和虛可觸及首启,在一定的條件下,是可以被回收的毅桃。
下面是個強引用例子:

Class A{
        StringBuffer str=new StringBuffer("Hello world");
        public void getStr(){
        StringBuffer str1=str;
        StringBuffer str2=str1;
    }
}

假設str是對象成員變量准夷,那么局部變量str1將被分配在棧上,而對象StringBuffer實例被分配在了堆上冕象。局部變量str1指向StringBuffer實例所在堆空間,通過str1可以操作該實例渐扮,那么str1就是StringBuilder實例的強引用掖棉。


圖片發(fā)自簡書App

強引用特點:

  • 強引用可以直接訪問目標對象膀估。
  • 強引用所指向的對象在任何時候都不會被系統(tǒng)回收,寧可拋出OOM異常察纯,也不會回收強引用所指的對象。
  • 強引用可導致內(nèi)存泄漏香伴。

軟引用--指向的對象可以被回收的引用

如果一個對象只有被軟引用所引用,當堆空間不足的時候回被回收即纲。下面的例子演示了軟引用會在系統(tǒng)堆內(nèi)存不足的時候被回收:

import java.lang.ref.SoftReference;

/**軟引用:
 * 軟引用是比強引用弱一點的引用類型博肋,如果一個對象僅僅只有軟引用,那么當堆空間不足時匪凡,
 * 就會被回收。
 *
 * 啟動參數(shù):-Xmx10m -Xms10m -XX:+UseSerialGC -XX:+PrintGCDetails -Xmn1m
 * Created by chenyang on 2017/2/2.
 */
public class SoftRef {
    public static class User{
        public User(int id,String name){
            this.id=id;
            this.name=name;
        }
        public int id;
        public String name;

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        User u=new User(1,"geym");//強引用
        SoftReference<User> userSoftRef=new SoftReference<User>(u);//建立軟引用
        u=null;//去除強引用

        System.out.println(userSoftRef.get());//軟引用存在
        System.gc();
        System.out.println("After GC:");
        System.out.println(userSoftRef.get());//gc后軟引用還存在唇跨,因為堆空間還是比較充足的。

        byte[] b=new byte[1024*924*7];//堆空間被其他對象占據(jù)轻绞,這是堆空間比較緊張
        System.gc();
        System.out.println(userSoftRef.get());//堆空間比較緊張時佣耐,軟引用的對象被回收。
    }
}

使用參數(shù)-Xmx10m運行上述代碼兼砖,得到:
[id=1,name=geym](從軟引用獲取數(shù)據(jù))
After GC:
[id=1,name=geym](GC沒有清除軟引用)
null (由于內(nèi)存緊張,軟引用雖然還在但是對象還是被回收了)

當內(nèi)存資源緊張時讽挟,軟引用指向的對象會被回收。所以耽梅,軟引用不會引起OOM
每一個軟引用都可以附帶一個引用隊列诅迷,當對象的可達性狀態(tài)發(fā)生改變時(由可達變?yōu)椴豢蛇_)佩番,軟引用對象就會進入引用隊列罢杉,通過這個引用隊列,就可以跟蹤對象的回收情況:

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

/**
 *
 * 每一個軟引用都可以附帶一個引用隊列滩租,當對象的可達性發(fā)生改變(由可達變?yōu)椴豢蛇_),
 * 軟引用對象就會進入引用隊列猎莲。通過這個引用隊列技即,可以跟蹤對象的回收情況益眉。
 *
 * 執(zhí)行參數(shù)-Xmx10m
 * Created by chenyang on 2017/2/2.
 */
public class SoftRefQ {
    public static class User{
        public User(int id,String name){
            this.id=id;
            this.name=name;
        }
        public int id;
        public String name;
    }
    static ReferenceQueue<User> softQueue=null;
    public static class CheckRefQueue extends Thread{
        @Override
        public void run() {
            while (true){
                if(softQueue!=null){
                    UserSoftReference obj=null;
                    try {
                        obj=(UserSoftReference)softQueue.remove();

                    }catch (InterruptedException e){
                        e.fillInStackTrace();
                    }

                    if(obj!=null){
                        System.out.println("user id"+obj.uid+" is delete");
                    }
                }
            }
        }
    }

    public static class UserSoftReference extends SoftReference<User>{
        int uid;
        public UserSoftReference(User referent,ReferenceQueue<? super User> q){
            super(referent,q);
            uid=referent.id;
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t=new CheckRefQueue();
        t.setDaemon(true);
        t.start();
        User u=new User(1,"chenyang");
        softQueue=new ReferenceQueue<User>();
        UserSoftReference userSoftRef=new UserSoftReference(u,softQueue);

        u=null;
        System.out.println(userSoftRef.get());
        System.gc();

        System.out.println("After GC:");
        System.out.println(userSoftRef.get());

        System.out.println("try to create byte array and GC");

        byte[] b=new byte[1024*925*7];
        System.gc();
        System.out.println(userSoftRef.get());

        Thread.sleep(1000);
    }
}

在創(chuàng)建一個軟引用時,指定了一個軟引用隊列年碘,當給定的對象實例被回收時,就會加入這個引用隊列屿衅,通過隊列可以跟蹤對象的回收情況:
使用參數(shù)-Xmx10m運行上述代碼,得到:
[id=1,name=geym](從軟引用獲取對象)
After GC:
[id=1,name=geym](GC沒有清除軟引用指向的對象)
try to create byte array and GC (創(chuàng)建大數(shù)組涡尘,耗盡內(nèi)存)
user id 1 is deleted (引用隊列探測到對象被刪除)
null (由于內(nèi)存緊張,軟引用雖然還在但是對象還是被回收了)

弱引用--指向的對象被GC發(fā)現(xiàn)即回收

弱引用是一種比軟引用弱的引用類型考抄。在GC時蔗彤,只要發(fā)現(xiàn)弱引用的對象不管堆空間如何都會將對象回收川梅。由于垃圾回收器的線程優(yōu)先級很低然遏,因此不一定很快發(fā)現(xiàn)持有弱引用的對象。這樣待侵,弱引用對象可以存在較長的時間。一旦弱引用被GC回收怨酝,就會加入一個注冊的引用隊列中。
弱引用例子:

/**
 * 弱引用是一種比軟引用較弱的引用類型凫碌。在系統(tǒng)gc時,只要發(fā)現(xiàn)弱引用盛险,不管系統(tǒng)堆空間使用情況如何,
 * 都會將對象進行回收苦掘。但是,由于垃圾回收期的線程通常優(yōu)先級很低惯驼,并不一定很快發(fā)現(xiàn)持有的弱引用對象。
 * 軟引用和弱引用非常適合那些可有可無的緩存數(shù)據(jù)祟牲。當系統(tǒng)堆內(nèi)存很低時,系統(tǒng)回收抖部。當系統(tǒng)堆內(nèi)存很高時说贝,
 * 這些緩存又能存在很長時間,從而起到加速系統(tǒng)的作用慎颗。
 * Created by chenyang on 2017/2/2.
 */
public class WeakRef {
    public static class User {
        public User(int id, String name) {
        this.id=id;
        this.name=name;
        }
        public int id;
        public String name;
    }

    public static void main(String[] args) {
        User u=new User(1,"chenyang");
        WeakReference<User> userWeakRef=new WeakReference<User>(u);
        u=null;
        System.out.println(userWeakRef.get());
        System.gc();
        //不管當前內(nèi)存空間是否夠用乡恕,都會回收它的內(nèi)存
        System.out.println("After GC:");
        System.out.println(userWeakRef.get());
    }
}

輸出為:
[id=1,name=geym](從弱引用獲取對象)
After GC:
null (弱對象被回收了)

軟引用和虛引用的使用場景:

軟引用和弱引用非常適合那些可有可無的緩存數(shù)據(jù)。當系統(tǒng)堆內(nèi)存不足時俯萎,系統(tǒng)回收傲宜。當系統(tǒng)堆內(nèi)存充足時,這些緩存又能存在很長時間夫啊,從而起到加速系統(tǒng)的作用函卒。

虛引用:

虛引用是所有引用類型中最弱的一個。一個吃魚虛引用的對象撇眯,跟沒有引用幾乎一樣谆趾,隨時會被GC回收。當試圖通過虛引用的get()方法取得強引用時沪蓬,總會失敗。而且来候,虛引用必須和引用隊列一起使用跷叉,它的作用在于跟蹤GC回收的過程。
當GC準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用云挟,就會在回收對象后梆砸,將這個虛引用加入引用隊列,以通知應用程序?qū)ο蟮幕厥涨闆r园欣。
下面給出一個例子帖世,使用虛引用跟蹤一個可復活對象的回收。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * Created by chenyang on 2017/2/2.
 */
public class TraceCanReliveObj {
    public static TraceCanReliveObj obj;
    static ReferenceQueue<TraceCanReliveObj> phantomQueue=null;
    public static class CheckRefQueue extends Thread{
        @Override
        public void run() {
            while (true){
                if(phantomQueue!=null){
                    PhantomReference<TraceCanReliveObj> objt=null;
                    try {
                        objt=(PhantomReference<TraceCanReliveObj>)phantomQueue.remove();
                    }catch (InterruptedException e){
                        e.fillInStackTrace();
                    }
                    if(objt!=null){
                        System.out.println("TraceCanReliveObj is delete");
                    }
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("CanReliveObj finalize called");
        obj=this;
    }

    @Override
    public String toString() {
        return "I am CanReliveObj";
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t=new CheckRefQueue();
        t.setDaemon(true);
        t.start();
        phantomQueue=new ReferenceQueue<TraceCanReliveObj>();
        obj=new TraceCanReliveObj();
        PhantomReference<TraceCanReliveObj> phantomRef=new PhantomReference<TraceCanReliveObj>(obj,phantomQueue);
        obj=null;
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj 是null");
        }else {
            System.out.println("obj 可用");
        }
        System.out.println("第二次gc");
        obj=null;
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj是null");
        }else {
            System.out.println("obj 可用");
        }
    }
}

輸出以下結(jié)果:
CanReliveObj finalize called (對象復活)
obj可用
第2次gc (第二次對象)
TraceCanReliveObj is delete (引用隊列捕獲到對象被回收)
obj是null

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沸枯,一起剝皮案震驚了整個濱河市日矫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绑榴,老刑警劉巖哪轿,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翔怎,居然都是意外死亡窃诉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門赤套,熙熙樓的掌柜王于貴愁眉苦臉地迎上來飘痛,“玉大人,你說我怎么就攤上這事容握⌒觯” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵唯沮,是天一觀的道長。 經(jīng)常有香客問我堪遂,道長介蛉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任溶褪,我火速辦了婚禮币旧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猿妈。我一直安慰自己吹菱,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布彭则。 她就那樣靜靜地躺著鳍刷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俯抖。 梳的紋絲不亂的頭發(fā)上输瓜,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼尤揣。 笑死搔啊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的北戏。 我是一名探鬼主播负芋,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗜愈!你這毒婦竟也來了旧蛾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤芝硬,失蹤者是張志新(化名)和其女友劉穎蚜点,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拌阴,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡绍绘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迟赃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陪拘。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纤壁,靈堂內(nèi)的尸體忽然破棺而出左刽,到底是詐尸還是另有隱情,我是刑警寧澤酌媒,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布欠痴,位于F島的核電站,受9級特大地震影響秒咨,放射性物質(zhì)發(fā)生泄漏喇辽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一雨席、第九天 我趴在偏房一處隱蔽的房頂上張望菩咨。 院中可真熱鬧,春花似錦陡厘、人聲如沸抽米。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽云茸。三九已至,卻和暖如春谤饭,著一層夾襖步出監(jiān)牢的瞬間查辩,已是汗流浹背胖笛。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宜岛,地道東北人长踊。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像萍倡,于是被迫代替她去往敵國和親身弊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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

  • 1.什么是垃圾回收列敲? 垃圾回收(Garbage Collection)是Java虛擬機(JVM)垃圾回收器提供...
    簡欲明心閱讀 89,557評論 17 311
  • 原文閱讀 前言 這段時間懈怠了阱佛,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了戴而,挺好的~給他們點贊凑术,這博客我...
    碼農(nóng)戲碼閱讀 5,993評論 2 31
  • [TOC] 內(nèi)存管理 一、托管堆基礎 在面向?qū)ο笾兴猓總€類型代表一種可使用的資源淮逊,要使用該資源,必須為代表資源的類...
    _秦同學_閱讀 3,818評論 0 3
  • Android 內(nèi)存管理的目的 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應用出現(xiàn)內(nèi)存泄漏的問題扶踊。簡單粗...
    晨光光閱讀 1,295評論 1 4
  • 讓我們先看看關(guān)于爆款的幾個問題: 為何浙江衛(wèi)視投入巨資打造《中國好聲音》秧耗?為何現(xiàn)在國內(nèi)足球俱樂部花大價錢引入大牌球...
    金剛king閱讀 2,167評論 0 50