秒懂ThreadLocal及其引發的內存泄露!

ThreadLocal是Java中的一種線程綁定機制,可以爲每一個使用該變量的線程都提供一個變量值的副本,並且每一個線程都可以獨立地改變自己的副本,而不會與其它線程的副本發生衝突,解決了變量併發訪問的衝突問題。

每個線程內部有一個 ThreadLocal.ThreadLocalMap 類型的成員變量 threadLocals,這個 threadLocals 存儲了與該線程相關的所有ThreadLocal 變量及其對應的值,也就是說,ThreadLocal 變量及其對應的值就是該Map中的一個 Entry,更直白地,threadLocals中每個Entry的Key是ThreadLocal 變量本身,而Value是該ThreadLocal變量對應的值。

 

ThreadLocal可能引起的內存泄露

  下面是ThreadLocalMap的部分源碼,我們可以看出ThreadLocalMap裏面對Key的引用是弱引用。那麼,就存在這樣的情況:當釋放掉對threadlocal對象的強引用後,map裏面的value沒有被回收,但卻永遠不會被訪問到了,因此ThreadLocal存在着內存泄露問題。

static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;
            
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
    }

  看下面的圖示, 實線代表強引用,虛線代表弱引用。每個thread中都存在一個map,map的類型是上文提到的ThreadLocal.ThreadLocalMap,該map中的key爲一個ThreadLocal實例。這個Map的確使用了弱引用,不過弱引用只是針對key,每個key都弱引用指向ThreadLocal對象。一旦把threadlocal實例置爲null以後,那麼將沒有任何強引用指向ThreadLocal對象,因此ThreadLocal對象將會被 Java GC 回收。但是,與之關聯的value卻不能回收,因爲存在一條從current thread連接過來的強引用。 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開,Current Thread、Map及value將全部被Java GC回收。

 

  所以,得出一個結論就是:只要這個線程對象被Java GC回收,就不會出現內存泄露。但是如果只把ThreadLocal引用指向null而線程對象依然存在,那麼此時Value是不會被回收的,這就發生了我們認爲的內存泄露。比如,在使用線程池的時候,線程結束是不會銷燬的而是會再次使用的,這種情形下就可能出現ThreadLocal內存泄露。  

  Java爲了最小化減少內存泄露的可能性和影響,在ThreadLocal進行get、set操作時會清除線程Map裏所有key爲null的value。所以最怕的情況就是,ThreadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,線程結束後被放回線程池中而不銷燬,那麼如果這個線程一直不被使用或者分配使用了又不再調用get/set方法,那麼這個期間就會發生真正的內存泄露。因此,最好的做法是:在不使用該ThreadLocal對象時,及時調用該對象的remove方法去移除ThreadLocal.ThreadLocalMap中的對應Entry。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章