Java引用

概述

Java的引用分為StrongReference杉编、SoftReference、WeakReference、PhantomReference和FinalReference;

其中StrongRference并沒有定義成接口忱详,而其余四種都有相應的接口定義;
這五種引用的使用場景如下:

  • 強引用
    最普遍的引用跺涤。如果一個對象具有強引用匈睁,垃圾回收器絕不會回收它。當內(nèi)存空 間不足桶错,Java虛擬機寧愿拋出OutOfMemoryError錯誤航唆,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足問題院刁。
  • 軟引用
    如果內(nèi)存空間足糯钙,則不回收;如果內(nèi)存空間不足退腥,則會被回收任岸;通常可以用來做為緩存狡刘;
  • 弱引用
    一旦發(fā)生GC,弱引用對象立即被回收演闭;
  • 虛引用
    虛引用不會影響對象的生命周期。如果一個對象僅持有虛引用颓帝,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收窝革;

SoftReference和WeakReference

上面提到SoftReference適合用來實現(xiàn)緩存购城,例如google的guava cache就使用到了WeakReference和SoftReference,在創(chuàng)建cache時可以通過如下方式指定用哪種引用:

//使用WeakReference
CacheBuilder.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(10, TimeUnit.MINUTES).weakKeys().weakValues()
                .build();
//使用SoftReference
 CacheBuilder.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(10, TimeUnit.MINUTES).softValues()
                .build();

創(chuàng)建SoftReference和WeakReference的構(gòu)造函數(shù)如下:

public WeakReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

可以看到可以傳入一個ReferenceQueue對象虐译,那么這個傳入到Queue對象到底有什么用呢瘪板?如果WeakReference所引用的對象被垃圾回收,Java虛擬機就會把這個WeakReference加入到與之關(guān)聯(lián)的Queue中漆诽;

PhantomReference

DirectByteBuffer是JDK提供的堆外內(nèi)存對象侮攀,其構(gòu)造函數(shù)如下:

  DirectByteBuffer(int cap) {   
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

其中的Cleaner對象繼承自PhantomReference:

public class Cleaner extends PhantomReference<Object>

那么定義成PhantomReference起什么作用呢锣枝?其實主要是為了回收堆外內(nèi)存,關(guān)于其具體的實現(xiàn)兰英,后面會進行介紹撇叁;

FinalReference

FinalReference有個子類Finalizer,這兩個類的包訪問級別都是default而不是public的畦贸,因此我們無法在代碼中直接調(diào)用陨闹;
說到FinalReference的使用,就免不了談及對象的創(chuàng)建:

instanceOop InstanceKlass::allocate_instance(TRAPS) {
  bool has_finalizer_flag = has_finalizer(); //判斷當前類是否包含非空的finalize方法
  int size = size_helper(); //確定要分配的內(nèi)存大小

  KlassHandle h_k(THREAD, this);

  instanceOop i;

  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}
instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
  if (TraceFinalizerRegistration) {
    tty->print("Registered ");
    i->print_value_on(tty);
    tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
  }
  instanceHandle h_i(THREAD, i);
  // Pass the handle as argument, JavaCalls::call expects oop as jobjects
  JavaValue result(T_VOID);
  JavaCallArguments args(h_i);
  methodHandle mh (THREAD, Universe::finalizer_register_method());
  JavaCalls::call(&result, mh, &args, CHECK_NULL);
  return h_i();
}

可以看到對象創(chuàng)建時薄坏,如果發(fā)現(xiàn)對象實現(xiàn)了finalize方法趋厉,則會調(diào)用Finalizer.register方法,將對象注冊到Finalizer的靜態(tài)屬性unfinalized上胶坠,形成Finalizer鏈表君账;

final class Finalizer extends FinalReference<Object> { 
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;

private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

}

可以看到FinalizerThread線程會從queue隊列中獲取Finalizer對象,調(diào)用其finalize方法沈善;queue即創(chuàng)建Reference時傳入的queue對象乡数;

那么,F(xiàn)inalizer什么時候會加入到queue隊列呢矮瘟?很容易想到瞳脓,當GC發(fā)生時,F(xiàn)inalizer對象會加入到queue隊列澈侠;

那么JVM是如何實現(xiàn)當對象回收時劫侧,將對象添加到queue呢?

# Reference
Reference對象是上述對象的基類哨啃,它的結(jié)構(gòu)如下:

private T referent;      
volatile ReferenceQueue<? super T> queue;//對象被回收后烧栋,Reference會被放入隊列
Reference next;
transient private Reference<T> discovered;  /* used by VM */
private static Reference<Object> pending = null;//JVM GC時,會將Reference對象設(shè)置到pending上拳球,discovered相當于next,指向下一個Reference對象

static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    if (waitForNotify) {//如果等待审姓,則調(diào)用lock.wait方法,當GC時將Reference添加到pending隊列時祝峻,會調(diào)用lock.notify方法魔吐,
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
        if (c != null) {//如果是Cleaner對象,調(diào)用其clean方法釋放資源
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

前面提到的DirectByteBuffer內(nèi)存的釋放就是通過JVM GC時將對象添加到pending對象莱找,而Reference中的ReferenceHandler線程會將pending中的對象添加到ReferenceQueue,同時如果發(fā)現(xiàn)是Cleaner對象酬姆,會調(diào)用clean方法釋放堆外內(nèi)存;

對于Finalizer對象奥溺,當被添加到ReferenceQueue時辞色,會通過FinalizerThread線程調(diào)用finalize方法;因此可以看到Finalizer對象至少要經(jīng)歷兩次垃圾回收才能被回收浮定,同時由于FinalizerThread線程的優(yōu)先級為Thread.MAX_PRIORITY - 2相满,因此當系統(tǒng)壓力較大時层亿,F(xiàn)inalizer對象可能要很久才能被回收;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末立美,一起剝皮案震驚了整個濱河市匿又,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悯辙,老刑警劉巖琳省,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躲撰,居然都是意外死亡针贬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門拢蛋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桦他,“玉大人,你說我怎么就攤上這事谆棱】煅梗” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵垃瞧,是天一觀的道長蔫劣。 經(jīng)常有香客問我,道長个从,這世上最難降的妖魔是什么脉幢? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮嗦锐,結(jié)果婚禮上嫌松,老公的妹妹穿的比我還像新娘。我一直安慰自己奕污,他們只是感情好萎羔,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碳默,像睡著了一般贾陷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘱根,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天昵宇,我揣著相機與錄音,去河邊找鬼儿子。 笑死,一個胖子當著我的面吹牛砸喻,可吹牛的內(nèi)容都是我干的柔逼。 我是一名探鬼主播蒋譬,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼愉适!你這毒婦竟也來了犯助?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤维咸,失蹤者是張志新(化名)和其女友劉穎剂买,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體癌蓖,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瞬哼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了租副。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坐慰。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖用僧,靈堂內(nèi)的尸體忽然破棺而出结胀,到底是詐尸還是另有隱情,我是刑警寧澤责循,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布糟港,位于F島的核電站,受9級特大地震影響院仿,放射性物質(zhì)發(fā)生泄漏秸抚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一意蛀、第九天 我趴在偏房一處隱蔽的房頂上張望耸别。 院中可真熱鬧,春花似錦县钥、人聲如沸秀姐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽省有。三九已至,卻和暖如春谴麦,著一層夾襖步出監(jiān)牢的瞬間蠢沿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工匾效, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舷蟀,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像野宜,于是被迫代替她去往敵國和親扫步。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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