某一天在某一個羣裏面的某個羣友忽然提出了一個問題:"threadlocal的key是弱引用,那麼在threadlocal.get()的時候,發生GC以後,key是不是null?"屏幕前的你能夠好好的想一想這個問題,在這裏我先賣個關子,先講講Java中引用和ThreadLocal的那些事。java
對於不少Java初學者來講,會把引用和對象給搞混淆。下面有一段代碼,git
User zhangsan = new User("zhangsan", 24);
這裏先提個問題zhangsan究竟是引用仍是對象呢?不少人會認爲zhangsan是個對象,若是你也是這樣認爲的話那麼再看一下下面一段代碼github
User zhangsan; zhangsan = new User("zhangsan", 24);
這段代碼和開始的代碼其實執行效果是一致的,這段代碼的第一行User zhangsan,定義了zhangsan,那你認爲zhangsan仍是對象嗎?若是你還認爲的話,那麼這個對象應該是什麼呢?的確,zhangsan其實只是一個引用,對JVM內存劃分熟悉的同窗應該熟悉下面的圖片:數組
其實zhangsan是棧中分配的一個引用,而new User("zhangsan", 24)是在堆中分配的一個對象。而'='的做用是用來將引用指向堆中的對象的。就像你叫張三但張三是個名字而已並非一個實際的人,他只是指向的你。緩存
咱們通常所說的引用其實都是代指的強引用,在JDK1.2以後引用不止這一種,通常來講分爲四種:強引用,軟引用,弱引用,虛引用。而接下來我會一一介紹這四種引用。app
上面咱們說過了 User zhangsan = new User("zhangsan", 24);這種就是強引用,有點相似C的指針。對強引用他的特色有下面幾個:工具
在Java中使用SoftReference幫助咱們定義軟引用。其構造方法有兩個:性能
public SoftReference(T referent); public SoftReference(T referent, ReferenceQueue<? super T> q);
兩個構造方法類似,第二個比第一個多了一個引用隊列,在構造方法中的第一個參數就是咱們的實際被指向的對象,這裏用新建一個SoftReference來替代咱們上面強引用的等號。 下面是構造軟引用的例子:學習
softZhangsan = new SoftReference(new User("zhangsan", 24));
若是某個對象他只被軟引用所指向,那麼他將會在內存要溢出的時候被回收,也就是當咱們要出現OOM的時候,若是回收了一波內存還不夠,這才拋出OOM,弱引用回收的時候若是設置了引用隊列,那麼這個軟引用還會進一次引用隊列,可是引用所指向的對象已經被回收。這裏要和下面的弱引用區分開來,弱引用是隻要有垃圾回收,那麼他所指向的對象就會被回收。下面是一個代碼例子:flex
public static void main(String[] args) { ReferenceQueue<User> referenceQueue = new ReferenceQueue(); SoftReference softReference = new SoftReference(new User("zhangsan",24), referenceQueue); //手動觸發GC System.gc(); Thread.sleep(1000); System.out.println("手動觸發GC:" + softReference.get()); System.out.println("手動觸發的隊列:" + referenceQueue.poll()); //經過堆內存不足觸發GC makeHeapNotEnough(); System.out.println("經過堆內存不足觸發GC:" + softReference.get()); System.out.println("經過堆內存不足觸發GC:" + referenceQueue.poll()); } private static void makeHeapNotEnough() { SoftReference softReference = new SoftReference(new byte[1024*1024*5]); byte[] bytes = new byte[1024*1024*5]; } 輸出: 手動觸發GC:User{name='zhangsan', age=24} 手動觸發的隊列:null 經過堆內存不足觸發GC:null 經過堆內存不足觸發GC:java.lang.ref.SoftReference@4b85612c
經過-Xmx10m設置咱們堆內存大小爲10,方便構造堆內存不足的狀況。能夠看見咱們輸出的狀況咱們手動調用System.gc並無回收咱們的軟引用所指向的對象,只有在內存不足的狀況下才能觸發。
在SoftReference的doc中有這麼一句話:
Soft references are most often used to implement memory-sensitive caches
也就是說軟引用常常用來實現內存敏感的高速緩存。怎麼理解這句話呢?咱們知道軟引用他只會在內存不足的時候才觸發,不會像強引用那用容易內存溢出,咱們能夠用其實現高速緩存,一方面內存不足的時候能夠回收,一方面也不會頻繁回收。在高速本地緩存Caffeine中實現了軟引用的緩存,當須要緩存淘汰的時候,若是是隻有軟引用指向那麼久會被回收。不熟悉Caffeine的同窗能夠閱讀深刻理解Caffeine
弱引用在Java中使用WeakReference來定義一個弱引用,上面咱們說過他比軟引用更加弱,只要發生垃圾回收,若這個對象只被弱引用指向,那麼就會被回收。這裏咱們就很少廢話了,直接上例子:
public static void main(String[] args) { WeakReference weakReference = new WeakReference(new User("zhangsan",24)); System.gc(); System.out.println("手動觸發GC:" + weakReference.get()); } 輸出結果: 手動觸發GC:null
能夠看見上面的例子只要垃圾回收一觸發,該對象就被回收了。
在WeakReference的註釋中寫到:
Weak references are most often used to implement canonicalizing mappings.
從中能夠知道弱引用更多的是用來實現canonicalizing mappings(規範化映射)。在JDK中WeakHashMap很好的體現了這個例子:
public static void main(String[] args) throws Exception { WeakHashMap<User, String> weakHashMap = new WeakHashMap(); //強引用 User zhangsan = new User("zhangsan", 24); weakHashMap.put(zhangsan, "zhangsan"); System.out.println("有強引用的時候:map大小" + weakHashMap.size()); //去掉強引用 zhangsan = null; System.gc(); Thread.sleep(1000); System.out.println("無強引用的時候:map大小"+weakHashMap.size()); } 輸出結果爲: 有強引用的時候:map大小1 無強引用的時候:map大小0
能夠看出在GC以後咱們在map中的鍵值對就被回收了,在weakHashMap中其實只有Key是弱引用作關聯的,而後經過引用隊列再去對咱們的map進行回收處理。
虛引用是最弱的引用,在Java中使用PhantomReference進行定義。弱到什麼地步呢?也就是你定義了虛引用根本沒法經過虛引用獲取到這個對象,更別談影響這個對象的生命週期了。在虛引用中惟一的做用就是用隊列接收對象即將死亡的通知。
public static void main(String[] args) throws Exception { ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference phantomReference = new PhantomReference(new User("zhangsan", 24), referenceQueue); System.out.println("什麼也不作,獲取:" + phantomReference.get()); } 輸出結果: 什麼也不作,獲取:null
在PhantomReference的註釋中寫到:
Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.
虛引用得最多的就是在對象死前所作的清理操做,這是一個比Java的finalization梗靈活的機制。 在DirectByteBuffer中使用Cleaner用來回收對外內存,Cleaner是PhantomReference的子類,當DirectByteBuffer被回收的時候未防止內存泄漏因此經過這種方式進行回收,有點相似於下面的代碼:
public static void main(String[] args) throws Exception { Cleaner.create(new User("zhangsan", 24), () -> {System.out.println("我被回收了,當前線程:{}"+ Thread.currentThread().getName());}); System.gc(); Thread.sleep(1000); } 輸出: 我被回收了,當前線程:Reference Handler
ThreadLocal是一個本地線程副本變量工具類,基本在咱們的代碼中隨處可見。這裏就不過多的介紹他了。
上面說了這麼多關於引用的事,這裏終於回到了主題了咱們的ThreadLocal和弱引用有什麼關係呢?
在咱們的Thread類中有下面這個變量:
ThreadLocal.ThreadLocalMap threadLocals
ThreadLocalMap本質上也是個Map,其中Key是咱們的ThreadLocal這個對象,Value就是咱們在ThreadLocal中保存的值。也就是說咱們的ThreadLocal保存和取對象都是經過Thread中的ThreadLocalMap來操做的,而key就是自己。在ThreadLocalMap中Entry有以下定義:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
能夠看見Entry是WeakReference的子類,而這個弱引用所關聯的對象正是咱們的ThreadLocal這個對象。咱們又回到上面的問題:
"threadlocal的key是弱引用,那麼在threadlocal.get()的時候,發生GC以後,key是不是null?"
這個問題晃眼一看,弱引用嘛,還有垃圾回收那確定是爲null,這實際上是不對的,由於題目說的是在作threadlocal.get()操做,證實其實仍是有強引用存在的。因此key並不爲null。若是咱們的強引用不存在的話,那麼Key就會被回收,也就是會出現咱們value沒被回收,key被回收,致使value永遠存在,出現內存泄漏。這也是ThreadLocal常常會被不少書籍提醒到須要remove()的緣由。
你也許會問看到不少源碼的ThreadLocal並無寫remove依然再用得很好呢?那實際上是由於不少源碼常常是做爲靜態變量存在的生命週期和Class是同樣的,而remove須要再那些方法或者對象裏面使用ThreadLocal,由於方法棧或者對象的銷燬從而強引用丟失,致使內存泄漏。
FastThreadLocal是Netty中提供的高性能本地線程副本變量工具。在Netty的io.netty.util中提供了不少牛逼的工具,後續會一一給你們介紹,這裏就先說下FastThreadLocal。
FastThreadLocal有下面幾個特色:
文章開頭的問題,爲何會被問出來,實際上是對弱引用和ThreadLocal理解不深致使,不少時候只記着一個若是是弱引用,在垃圾回收時就會被回收,就會致使把這個觀念先入爲主,沒有作更多的分析思考。因此你們再分析一個問題的時候仍是須要更多的站在不一樣的場景上作更多的思考。
最後這篇文章被我收錄於JGrowing-Java基礎篇,一個全面,優秀,由社區一塊兒共建的Java學習路線,若是您想參與開源項目的維護,能夠一塊兒共建,github地址爲:https://github.com/javagrowing/JGrowing 麻煩給個小星星喲。
若是你們以爲這篇文章對你有幫助,你的關注和轉發是對我最大的支持,O(∩_∩)O: