這兩天又重新學習了一下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
參考: