1. 程式人生 > 實用技巧 >理解ThreadLocalMap記憶體洩露問題

理解ThreadLocalMap記憶體洩露問題

這裡所說的ThreadLocal的記憶體洩露問題,其實都是從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;
    }
}

這裡簡單介紹一下Java內的四大引用:

  • 強引用:Java中預設的引用型別,一個物件如果具有強引用那麼只要這種引用還存在就不會被回收。比如String str = new String("Hello ThreadLocal");,其中str就是一個強引用,當然,一旦強引用出了其作用域,那麼強引用隨著方法彈出執行緒棧,那麼它所指向的物件將在合適的時機被JVM垃圾收集器回收。
  • 軟引用:如果一個物件具有軟引用,在JVM發生記憶體溢位之前(即記憶體充足夠使用),是不會GC這個物件的;只有到JVM記憶體不足的時候才會呼叫垃圾回收期回收掉這個物件。軟引用和一個引用佇列聯合使用,如果軟引用所引用的物件被回收之後,該引用就會加入到與之關聯的引用佇列中。
  • 弱引用:這裡討論ThreadLocalMap中的Entry類的重點,如果一個物件只具有弱引用,那麼這個物件就會被垃圾回收器回收掉(被弱引用所引用的物件只能生存到下一次GC之前,當發生GC時候,無論當前記憶體是否足夠,弱引用所引用的物件都會被回收掉)。弱引用也是和一個引用佇列聯合使用,如果弱引用的物件被垃圾回收期回收掉,JVM會將這個引用加入到與之關聯的引用佇列中。若引用的物件可以通過弱引用的get方法得到,當引用的物件被回收掉之後,再呼叫get方法就會返回null。
  • 虛引用:虛引用是所有引用中最弱的一種引用,其存在就是為了將關聯虛引用的物件在被GC掉之後收到一個通知。

我們從ThreadLocal的內部靜態類Entry的程式碼設計可知,ThreadLocal的引用k通過構造方法傳遞給了Entry類的父類WeakReference的構造方法,從這個層面來說,可以理解ThreadLocalMap中的鍵是ThreadLocal的所引用。

當一個執行緒呼叫ThreadLocal的set方法設定變數的時候,當前執行緒的ThreadLocalMap就會存放一個記錄,這個記錄的鍵為ThreadLocal的弱引用,value就是通過set設定的值,這個value值被強引用。

如果當前執行緒一直存在且沒有呼叫該ThreadLocal的remove方法,如果這個時候別的地方還有對ThreadLocal的引用,那麼當前執行緒中的ThreadLocalMap中會存在對ThreadLocal變數的引用和value物件的引用,是不會釋放的,就會造成記憶體洩漏。

考慮這個ThreadLocal變數沒有其他強依賴,如果當前執行緒還存在,由於執行緒的ThreadLocalMap裡面的key是弱引用,所以當前執行緒的ThreadLocalMap裡面的ThreadLocal變數的弱引用在垃圾回收的時候就被回收,但是對應的value還是存在的這就可能造成記憶體洩漏(因為這個時候ThreadLocalMap會存在key為null但是value不為null的entry項)。

總結:ThreadLocalMap中的Entry的key使用的是ThreadLocal物件的弱引用,在沒有其他地方對ThreadLocal依賴,ThreadLocalMap中的ThreadLocal物件就會被回收掉,但是對應的值不會被回收,這個時候Map中就可能存在key為null但是值不為null的項,所以在使用ThreadLocal的時候要養成及時remove的習慣。