在閱讀本文以前最好對 Reference 框架有一個總體的把握,能夠參考我上一篇博客 Reference 框架概覽 ;本文主要講了 Reference
的子類實現和應用(SoftReference,WeakReference,PhantomReference
);html
Java 引用的強弱關係:StrongReference > SoftReference > WeakReference > PhantomReference
java
強引用:咱們一般使用的引用,形如Object o = new Object();
git
此時從 stack 中的 o,到 heap 中的 Object 就是強引用;其餘引用強弱的斷定規則,能夠查看我上一篇博客 Reference 框架概覽 ;github
軟引用:能夠用來表示一些有用但非必須的對象;JVM 會根據使用率和剩餘堆空間大小來公共決定何時回收 SoftReference;JVM 保證在拋出 OOM 以前會再次掃描回收這些軟引用,若是回收後內存仍不足纔會拋出 OOM;因此在源碼的註釋中也寫了 SoftReference 適合實現內存敏感的緩存;緩存
public class SoftReference<T> extends Reference<T> { /** * Timestamp clock, updated by the garbage collector */ 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. */ private long timestamp; public SoftReference(T referent) { super(referent); this.timestamp = clock; } public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock; } public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; } }
看上面的代碼,SoftReference 與 Reference 相比多了兩個時間戳 clock,timestamp
,而且會在每次 get
的時候更新時間戳;app
static
修飾的,是全部 SoftReference 共有,由 JVM 維護;回收策略框架
上面提到 SoftReference 的回收是由使用率和剩餘堆空間大小來公共決定的,那麼它是怎麼實現的呢?ide
openjdk/hotspot/src/share/vm/memory/referencePolicy.cppoop
// Capture state (of-the-VM) information needed to evaluate the policy void LRUCurrentHeapPolicy::setup() { _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB; assert(_max_interval >= 0,"Sanity check"); } // The oop passed in is the SoftReference object, and not // the object the SoftReference points to. bool LRUCurrentHeapPolicy::should_clear_reference(oop p, jlong timestamp_clock) { jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p); assert(interval >= 0, "Sanity check"); // The interval will be zero if the ref was accessed since the last scavenge/gc. if(interval <= _max_interval) { return false; } return true; }
根據上面的代碼能夠大體知道:post
this.timestamp = clock;
,因此get 以後 idle 時間歸零),若是大於最大 idle 時間則清除;咱們能夠簡單測試一下,啓動參數:-XX:SoftRefLRUPolicyMSPerMB=2 -Xmx10M -XX:+PrintCommandLineFlags -verbose:gc
;
-XX:SoftRefLRUPolicyMSPerMB=2
:能夠參照上面的計算過程調節 SoftReference 的回收頻率;-Xmx10M
:爲最大堆內存,一樣能夠自行調節,-verbose:gc
:打開 GC 日誌,-XX:+PrintCommandLineFlags
:打印 JVM 啓動參數;private static void test03() throws InterruptedException { ReferenceQueue queue = new ReferenceQueue(); Object o = new Object() { @Override public String toString() { return "zhangsan"; } }; Reference softRef = new SoftReference(o, queue); new Monitor(queue).start(); o = null; System.gc(); log.info("o=null, referent:{}", softRef.get()); byte[] bytes = new byte[3 * 1024 * 1024]; System.gc(); log.info("After GC, referent:{}", softRef.get()); Thread.sleep(2000); System.gc(); log.info("After GC, referent:{}", softRef.get()); } private static class Monitor extends Thread { ReferenceQueue queue; public Monitor(ReferenceQueue queue) { this.queue = queue; } @Override public void run() { while (true) { try { log.info("remove reference:{}", queue.remove().toString()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
// 打印:
[main] o=null, referent:zhangsan [main] After GC, referent:zhangsan [main] After GC, referent:null [Thread-0] remove reference:java.lang.ref.SoftReference@bcffe9a
根據不一樣的參數設置會出現不一樣的狀況,你們能夠自行調節參數,驗證上面的計算規則;另外若是-XX:SoftRefLRUPolicyMSPerMB=0
,那麼 SoftReference 就應該和 WeakReference 差很少了,至因而否徹底一致,就留到之後查看 JVM 的時候再肯定了;
弱引用:被弱引用關聯的對象只能生存到下一次 GC,當 GC 的時候不管內存是否足夠,使用是否頻繁都會被清除;一樣源碼註釋裏面也寫了 WeakReference 適合實現 canonicalizing mappings,好比 WeakHashMap;
public class WeakReference<T> extends Reference<T> { public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
簡單測試,啓動參數:-Xmx300M -XX:+PrintCommandLineFlags -verbose:gc
;
private static void test04() { ReferenceQueue queue = new ReferenceQueue(); Object o = new Object() { @Override public String toString() { return "zhangsan"; } }; Reference ref = new WeakReference(o, queue); new Monitor(queue).start(); o = null; log.info("Before GC, referent:{}", ref.get()); System.gc(); log.info("After GC, referent:{}", ref.get()); }
// 打印:
[main] Before GC, referent:zhangsan [main] After GC, referent:null [Thread-0] remove reference:java.lang.ref.WeakReference@67ac4ff0
能夠看到在內存足夠的時候,referent 被清除,WeakReference 在下次 GC 的時候隨機被清除,而且 ReferenceQueue 也收到了事件通知;
虛引用:最弱的一種引用關係,虛引用對一個對象的生命週期徹底沒有影響,設置虛引用的惟一目的就是獲得 referent 被回收的事件通知;
public class PhantomReference<T> extends Reference<T> { public T get() { return null; } public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
從源碼也能看到 get 的時候,永遠返回 null;
一樣簡單測試一下,
private static void test06() { ReferenceQueue queue = new ReferenceQueue(); Object o = new Object() { @Override public String toString() { return "zhangsan"; } }; Reference ref = new PhantomReference(o, queue); new Monitor(queue).start(); o = null; log.info("Before GC, referent:{}", ref.get()); System.gc(); log.info("After GC, referent:{}", ref.get()); }
// 打印:
[main] Before GC, referent:null [main] After GC, referent:null [Thread-0] remove reference:java.lang.ref.PhantomReference@661a5fff
能夠看到 PhantomReference.get()
始終爲 null,而且當 referent 被回收的時候,而且 ReferenceQueue 也收到了事件通知;
此外 PhantomReference 和其餘引用還有一個很大的不一樣,在 ReferenceQueue 中 JVM 並不會幫咱們把 referent 字段置爲空;
private static void test07() { ReferenceQueue queue = new ReferenceQueue(); Object o = new Object() { @Override public String toString() { return "zhangsan"; } }; Reference ref = new PhantomReference(o, queue); new Monitor2(queue).start(); o = null; log.info("Before GC, referent:{}", ref.get()); System.gc(); log.info("After GC, referent:{}", ref.get()); } private static class Monitor2 extends Thread { ReferenceQueue queue; public Monitor2(ReferenceQueue queue) { this.queue = queue; } @Override public void run() { try { while (true) { Reference ref = queue.poll(); log.info("remove reference:{}", ref); if (ref != null) { Field field = Reference.class.getDeclaredField("referent"); field.setAccessible(true); log.info("ReferenceQueue get Referent:{}", field.get(ref)); ref.clear(); break; } } } catch (Exception e) { e.printStackTrace(); } } }
// 打印:
[main] Before GC, referent:null [main] After GC, referent:null [Thread-0] remove reference:null [Thread-0] remove reference:java.lang.ref.PhantomReference@7b4cba2 [Thread-0] ReferenceQueue get Referent:zhangsan
這裏能夠看到從 ReferenceQueue 中取出來的 Reference 仍然能夠取到引用對象,即 referent;可是在其餘引用中打印爲 null,這裏能夠將上面例子中的 Monitor 改成 Monitor2 測試;
Cleaner:
在Reference.tryHandlePending()
裏面提到的,主要用於替代Object.finalize()
;
public class Cleaner extends PhantomReference<Object> { private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>(); static private Cleaner first = null; private Cleaner next = null, prev = null; private final Runnable thunk; private Cleaner(Object referent, Runnable thunk) { super(referent, dummyQueue); this.thunk = thunk; } public static Cleaner create(Object ob, Runnable thunk) { if (thunk == null) return null; return add(new Cleaner(ob, thunk)); } private static synchronized Cleaner add(Cleaner cl) { if (first != null) { cl.next = first; first.prev = cl; } first = cl; return cl; } private static synchronized boolean remove(Cleaner cl) { } public void clean() { if (!remove(this)) return; try { thunk.run(); } catch (final Throwable x) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (System.err != null) new Error("Cleaner terminated abnormally", x) .printStackTrace(); System.exit(1); return null; }}); } } }
從代碼能夠看到,
next、prev
不一樣於 Reference 中的 next,他們組成了一個雙向鏈表;Reference.tryHandlePending()
;dummyQueue
域)的做用再也不是提供入隊和事件監聽功能,而僅僅是保證 GC 不會自動將 Cleaner 給回收了;clean
操做;finalize
方法時,JVM 會將他包裝在 FinalReference 裏面,裏面的細節比較多,而且通常不建議使用,因此暫時沒寫;http://www.importnew.com/21628.html
https://www.jianshu.com/p/95a4931ebf01
http://www.javashuo.com/article/p-sqolikpq-kn.html