Java引用

概述

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

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

  • 強引用
    最普遍的引用。如果一個對象具有強引用,垃圾回收器絕不會回收它。當內存空 間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。
  • 軟引用
    如果內存空間足,則不回收;如果內存空間不足,則會被回收;通常可以用來做為緩存;
  • 弱引用
    一旦發生GC,弱引用對象立即被回收;
  • 虛引用
    虛引用不會影響對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收;

SoftReference和WeakReference

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

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

創建SoftReference和WeakReference的構造函數如下:

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

可以看到可以傳入一個ReferenceQueue對象,那么這個傳入到Queue對象到底有什么用呢?如果WeakReference所引用的對象被垃圾回收,Java虛擬機就會把這個WeakReference加入到與之關聯的Queue中;

PhantomReference

DirectByteBuffer是JDK提供的堆外內存對象,其構造函數如下:

  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起什么作用呢?其實主要是為了回收堆外內存,關于其具體的實現,后面會進行介紹;

FinalReference

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

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

  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();
}

可以看到對象創建時,如果發現對象實現了finalize方法,則會調用Finalizer.register方法,將對象注冊到Finalizer的靜態屬性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對象,調用其finalize方法;queue即創建Reference時傳入的queue對象;

那么,Finalizer什么時候會加入到queue隊列呢?很容易想到,當GC發生時,Finalizer對象會加入到queue隊列;

那么JVM是如何實現當對象回收時,將對象添加到queue呢?

# Reference
Reference對象是上述對象的基類,它的結構如下:

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對象設置到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) {//如果等待,則調用lock.wait方法,當GC時將Reference添加到pending隊列時,會調用lock.notify方法,
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
        if (c != null) {//如果是Cleaner對象,調用其clean方法釋放資源
            c.clean();
            return true;
        }

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

前面提到的DirectByteBuffer內存的釋放就是通過JVM GC時將對象添加到pending對象,而Reference中的ReferenceHandler線程會將pending中的對象添加到ReferenceQueue,同時如果發現是Cleaner對象,會調用clean方法釋放堆外內存;

對于Finalizer對象,當被添加到ReferenceQueue時,會通過FinalizerThread線程調用finalize方法;因此可以看到Finalizer對象至少要經歷兩次垃圾回收才能被回收,同時由于FinalizerThread線程的優先級為Thread.MAX_PRIORITY - 2,因此當系統壓力較大時,Finalizer對象可能要很久才能被回收;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容