java中的Reference


這兩天又重新學習了一下Reference,根據網上的資源做了匯總。

Java中的引用主要有4種:

  強引用 StrongReference: Object obj = new Object(); obj就為一個強引用,obj=null后, 該對象可能會被JVM回收

  軟引用 SoftReference: 在內存不夠用的時候,才會回收軟引用的對象。

Object obj = new Object(); SoftReference<Object> softref = new SoftReference<Object>(obj); obj = null;

  弱引用 WeakReference: new出來的對象沒有強引用連接時,下一次GC時,就會回收該對象。

Object obj = new Object(); WeakReference<Object> weakRef = new WeakReference<Object>(obj); obj = null;

  虛引用 PhantomReference: 與要與ReferenceQueue配合使用,它的get()方法永遠返回null

JDK中的 java.lang.ref包:

  java.lang.ref包下主要都是reference相關的類,主要包括:

    FinalReference: 代表強引用,使沒法直接使用。

    Finalizer:FinalReference的子類,主要處理finalize相關的工作

    PhantomReference: 虛引用

    Reference: 引用基類,abstract的

    ReferenceQueue: 引用軌跡隊列

    SoftReference:軟引用

    WeakedReference: 弱引用

ReferenceQueue

  引用隊列,在檢測到適當的可到達性更改后,垃圾回收器將已注冊的引用對象添加到該隊列中。

  

static ReferenceQueue NULL = new Null();    //初始化為null
static ReferenceQueue ENQUEUED = new Null();

static private class Lock { };
private Lock lock = new Lock();    //初始化為null
private volatile Reference<? extends T> head = null;    //Queue的header設定為null
private long queueLength = 0;

  queue主要有幾種操作: enqueue, poll(非阻塞), remove(阻塞)

  enqueue的操作是添加一個對象到隊列中。

boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
//本方法只能在Reference類中調用
        synchronized (r) {
 //防止將同一個Reference兩次入隊列
            if (r.queue == ENQUEUED) return false;
            synchronized (lock) {
//該對象入隊列后,將該對象的queue對象置為 ENQUEUED
                r.queue = ENQUEUED;
                r.next = (head == null) ? r : head;
                head = r;
                queueLength++;
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(1);
                }
                lock.notifyAll();
                return true;
            }
        }
    }

入隊列的實際操作就是將Reference對象r放到header的第一位

  poll的方法為:public Reference<? extends T> poll(),最終會調用 private Reference<? extends T> reallyPoll();具體操作與添加相反,若隊列沒有什值,直接返回null。

  remove的方法:public Reference<? extends T> remove(),最終會調用public Reference<? extends T> remove(long timeout),傳遞超時時間.

//阻塞,直到返回結果
public Reference<? extends T> remove(long timeout)
        throws IllegalArgumentException, InterruptedException
    {
        if (timeout < 0) {
            throw new IllegalArgumentException("Negative timeout value");
        }
        synchronized (lock) {
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
 //死循環
            for (;;) {
                lock.wait(timeout);
                r = reallyPoll();
                if (r != null) return r;
//若傳遞的等待時間不為0,說明等該timeout后也沒有reference入隊列,返回null,
//若timeout為0,則循環,直接隊列有數據
                if (timeout != 0) return null;
            }
        }
    }

SoftReference

   SoftReference繼承了抽象類Reference,自己內容有兩個屬性:

    /**
     * Timestamp clock, updated by the garbage collector
     */
//GC 會更新這一個靜態變量的值
    static private long clock;

    /**
     * Timestamp updated by each invocation of the get method.  The VM may use
     * this field when selecting soft references to be cleared, but it is not
     * required to do so.
     */
//每次調用get方法時會更新timestampe
// this.timestamp = clock;
    private long timestamp;

  主要的邏輯還是在Reference類中:

  Reference提供了兩個構造方法:

  

  Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

  同時有一些私有的屬性:

  

  private T referent;         /* Treated specially by GC */

    ReferenceQueue<? super T> queue;

    Reference next;
    transient private Reference<T> discovered;  /* used by VM */


    static private class Lock { };
    private static Lock lock = new Lock();

  最有意思的是有段static的代碼塊

  

 /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object.
     */
//static類型,全局只有一個,GC一個Reference時,會把該Reference放到pending中,由於Reference有一個next屬性,該處可能會是一些被GC過的引用的隊列
    private static Reference pending = null;

    /* High-priority thread to enqueue pending References
     */
    private static class ReferenceHandler extends Thread {

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            for (;;) {

                Reference r;
                synchronized (lock) {
                    if (pending != null) {
//若pending不為null,將每個reference取出,
                        r = pending;
                        Reference rn = r.next;
                        pending = (rn == r) ? null : rn;
                        r.next = r;
                    } else {
    //若pending為null,等侍
                        try {
                            lock.wait();
                        } catch (InterruptedException x) { }
                        continue;
                    }
                }

                // Fast path for cleaners
                if (r instanceof Cleaner) {
                    ((Cleaner)r).clean();
                    continue;
                }

                ReferenceQueue q = r.queue;
//找到該Reference對象中的Queue,將自己添加到ReferenceQueue中
//這樣就實現了軟引用對象被回收后,在ReferenceQueue中就可以獲取到。
                if (q != ReferenceQueue.NULL) q.enqueue(r);
            }
        }
    }
//啟動一個線程(Reference Handler)處理引用
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
    }

  其它的一些操作如:get, clear, isEnqueued, enqueue,都是簡單的操作。

WeakReference

   WeakReference也繼承了Reference對象。

WeakReference與SoftReference

  這兩上類都繼承了Reference對象,基本的操作都一樣的。唯一的區別就是SoftReference內部的屬性(private long timestamp; 在每次get的時候會更新該值),VM有可能要GC的時候使用該字段來判斷。這就和兩類引用的區別相關連了,WeakReference每次GC時就會直接回收該引用的對象,而SoftReference只有在內存不夠用的時候才會回收對象,而回收哪一個對象,可能就需要這個字段來區分。

 FinalReference

 

class FinalReference<T> extends Reference<T> {

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

}

  這個類的權限是package,我們沒法new出一個對象來使用。並且也只是繼承了Reference類,沒有別的特殊的操作。但同一個包有一類Finalizer,Finalizer繼承了FinalReference,它也是一個package權限的類,同時是final的,不能被繼承了。

    //全局一個ReferenceQueue
    private static ReferenceQueue queue = new ReferenceQueue();
    //全局只有一個unfinalized,也可以組成對象鏈
    private static Finalizer unfinalized = null;
    private static final Object lock = new Object();

    private Finalizer
        next = null,
        prev = null;

    // 將自己添加到unfinalized隊列中
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }
//私有的構造方法
    private Finalizer(Object finalizee) {
//任何Finalizer對象的GC后都會到queue中
        super(finalizee, queue);
        add();
    }

/* Invoked by VM */
static void register(Object finalizee) {
  new Finalizer(finalizee);
}

 

   register執行的時候會new出對象,只有f類才會被JVM調用register。

  f類:實現了finalize方法並且非空的類。類的加載過程就已經標記為是否f類。

  Finalizer類最后有一些static的代碼塊:

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 {
//從ReferenceQueue中取出對象,執行對象的runFinalizer方法
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
    //線程優先級低
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }

  runFinalize方法會通過JVM調用object的finalize方法

private boolean hasBeenFinalized() {
        return (next == this);
    }
private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
//若next==this,則表明this對象已經從unfinalized對象鏈中移除,已經執行過一次runFinalizer了
            if (hasBeenFinalized()) return;
//將該對象從unfinalized對象鏈中移除   
             remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
//通過JDK調用對象的finalize方法
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }

  1 當GC發生時,GC算法會判斷f累對象是不是只被Finalizer類引用

  2若這個累僅僅被Finalizer對象引用,說明這個對象在不就的將來會被回收,現在可以執行他的inalize方法了。

  3 將這個隊形放到Finalizer類的ReferenceQueue中,但這個f類對象其實並沒有被回收,因為Finalizer這個類還對他們保持引用。

  4 GC完成之前,JVM會調用ReferenceQueue中lock對象的notify方法,

  5 Finalizer的守護線程可能會被喚醒,從Queue中取出對象(remove),執行該Finalizer對象的runFinalizer方法(1 將自己從unfinalized對象鏈中去除,2 執行引用對象的finalize方法)

  6 下次GC時回收這個對象。

 

 

  • f對象因為Finalizer的引用而變成了一個臨時的強引用,即使沒有其他的強引用,還是無法立即被回收;
  • f對象至少經歷兩次GC才能被回收,因為只有在FinalizerThread執行完了f對象的finalize方法的情況下才有可能被下次GC回收,而有可能期間已經經歷過多次GC了,但是一直還沒執行f對象的finalize方法;
  • CPU資源比較稀缺的情況下FinalizerThread線程有可能因為優先級比較低而延遲執行f對象的finalize方法;
  • 因為f對象的finalize方法遲遲沒有執行,有可能會導致大部分f對象進入到old分代,此時容易引發old分代的GC,甚至Full GC,GC暫停時間明顯變長;
  • f對象的finalize方法被調用后,這個對象其實還並沒有被回收,雖然可能在不久的將來會被回收。
  • You can see preference processing times (and number of references) in GC logs with -XX:+PrintReferenceGC

 

 參考:

 深入探討 java.lang.ref 包

JVM源碼分析之FinalReference完全解讀 

 

 

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM