1. 程式人生 > 其它 >ThreadLocal記憶體洩漏的緣故

ThreadLocal記憶體洩漏的緣故

我們通常會用ThreadLocal用來儲存當前執行緒的資料。但是在實際使用的時候注意使用完之後及時呼叫例項的remove方法。

他把資料繫結到當前執行緒程式碼原理是這麼幹的。

首先Thread類裡面儲存ThreadLocalMap。

然後ThreadLocalMap是ThreadLocal的內部類。

可以看到ThreadLocalMap裡面entry是以ThreadLocal作為key值的。

他儲存資料到當前執行緒是這麼幹的。

相當於ThreadLocal本身不儲存資料,而是作為key放在其內部類ThreadLocalMap中,資料作為value放在ThreadLocalMap中。

要取資料的時候。通過當前執行緒獲取其ThreadLocalMap,然後根據threadlocal例項獲取對應的資料。

從上面可以看出,ThreadLocalMap是裡面entry是以ThreadLocal作為key值的,而且還是弱引用的。意味著下次gc的時候,不管jvm堆記憶體是否足夠,threadlocal都會被回收掉,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前執行緒再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成記憶體洩漏。

我們復現一下ThreadLocal記憶體洩漏的現象。

public class Test {
    public static void main(String[] args) {
        new Thread(() -> {
            // ThreadLocal放置到程式碼塊中,為證明threadLocal 會被GC回收掉
            {
                ThreadLocalTest<ValueVO> threadLocal = new ThreadLocalTest<>();
                threadLocal.set(new ValueVO());
                System.out.println(threadLocal.get());
            }
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(1000);
                    //每秒加10M 堆記憶體 能看出記憶體增長趨勢
                    //GC時,a臨時變數會被回收
                    int[] a = new int[1024 * 1024 * 10];
                    if (i++ > 100) {
                        break;
                    }
                    i++;
                } catch (InterruptedException e) {
                }
            }

        }).start();
    }
}

class ValueVO {
    @Override
    public String toString() {
        return "hello,world";
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("value deading");
    }
}

/**
 * 繼承ThreadLocal,實現finalize方法,垃圾回收器準備釋放記憶體的時候,會先呼叫finalize()。
* * @param <T> */ class ThreadLocalTest<T> extends ThreadLocal<T> { @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("key deading"); } }

然後發現ThreadLocalMap的作為key值的ThreadLocal被垃圾回收了,作為value的當前執行緒的ValueVo的資料最後都沒被回收。

解決辦法:每次使用完ThreadLocal,都呼叫它的remove()方法,清除資料。