從JDK1.2開始,Java中的引用類型分為四種,分別是:
1.強引用(StrongReference)
這種引用是平時開發中最常用的,例如
String strong = new String("Strong Reference"),
當一個實例對象具有強引用時,垃圾回收器不會回收該對象,當內存不足時,寧願拋出OutOfMemeryError異常也不會通過回收強引用的對象,因為JVM認為強引用的對象是用戶正在使用的對象,它無法分辨出到底該回收哪個,強行回收有可能導致系統嚴重錯誤。
2.軟引用(SoftRefernce)
如果一個對象只有軟引用,
那么只有當內存不足時,JVM才會去回收該對象,其他情況不會回收。軟引用可以結合ReferenceQueue來使用,當由於系統內存不足,導致軟引用的對象被回收了,JVM會把這個軟引用加入到與之相關聯的ReferenceQueue中。
ReferenceQueue referenceQueue = new ReferenceQueue(); SoftReference<Book> softReference = new SoftReference<>(new Book(), referenceQueue); Book book = softReference.get(); Reference reference = referenceQueue.poll();
當系統內存不足時,觸發gc,這個Book就會被回收,reference 將不為null。
3.弱引用(WeakReference)
只有弱引用的對象,當JVM觸發gc時,就會回收該對象。與軟引用不同的是,不管是否內存不足,弱引用都會被回收。弱引用可以結合ReferenceQueue來使用,當由於系統觸發gc,導致軟引用的對象被回收了,JVM會把這個弱引用加入到與之相關聯的ReferenceQueue中,不過由於垃圾收集器線程的優先級很低,所以弱引用不一定會被很快回收。下面通過一個主動觸發gc的例子來驗證此結論。
ReferenceQueue referenceQueue = new ReferenceQueue(); WeakReference<Book> weakReference = new WeakReference(new Book(), referenceQueue); Book book = softReference.get(); System.gc(); //Runtime.getRuntime().gc(); Reference reference = referenceQueue.poll();

當然這不是每次都能復現,因為我們調用System.gc()只是告訴JVM該回收垃圾了,但是它什么時候做還是不一定的,但就我測試來看,只要多寫幾次System.gc(),復現的概率還是很高的。
PS,
ThreadLocalMap中的靜態內部類Entry類就是繼承了WeakRefence<ThreadLocal>這個類在構造方法中
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { //調用父類WeakReference的構造方法 super(k); value = v; } }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) //ThreadLocalMap將ThreadLocal作為key,(注意這個Key有點特殊,這個Key被弱引用關聯) map.set(this, value); else createMap(t, value); }
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //e最終獲取的是其實是被弱引用WeakRerference關聯的ThreadLocal ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
//Reference中的Get方法 public T get() { return this.referent; }
注意:
由於Thread 線程對象都具有成員變量ThreadLocal.ThreadLocalMap,
而ThreadLocalMap中的key就是當前ThreadLocal對象,而value就是我們想要和線程上下文綁定的數據,
同時ThreadLocal又被弱引用關聯,
因此在線程生命周期結束時,其成員變量ThreadLocal.ThreadLocalMap,也會為null,此時內部的Entry會做額外的操作將value置為null,
這樣就滿足了弱引用被回收的條件:當且僅當Entry的key持有弱引用時,該ThreadLocal就會被回收,解決了ThreadLocal對象內存泄漏的問題;
如果一個對象只有虛引用在引用它,垃圾回收器是可以在任意時候對其進行回收的,虛引用主要用來跟蹤對象被垃圾回收器回收的活動,當被回收時,JVM會把這個弱引用加入到與之相關聯的ReferenceQueue中。與軟引用和弱引用不同的是,虛引用必須有一個與之關聯的ReferenceQueue,通過phantomReference.get()得到的值為null,試想一下,如果沒有ReferenceQueue與之關聯還有什么存在的價值呢?
PhantomReference<Book> phantomReference = new PhantomReference<>(new Book(), referenceQueue); Book book = phantomReference.get(); //此值為null Reference reference = referenceQueue.poll();
