2010-10-06 45 views

回答

49

的Javadoc這樣說:

「每個線程持有,只要一個隱含參考其線程局部變量的副本線程是活動的並且ThreadLocal實例是可訪問的;後一線程消失,所有的線程局部實例的副本都會被垃圾回收(除非存在對這些副本的其他引用)。

如果您的應用程序或者(如果你正在談論的請求的線程)容器使用線程池意味着線程不會死亡,如果有必要,您需要處理線程本地人自己。唯一干淨的方法是調用ThreadLocal.remove()方法。

有你可能要清理線程局部變量在一個線程池中的線程有兩個原因:

  • ,以防止內存(或者假設資源)泄漏,或
  • ,以防止信息的意外泄露一個請求通過線程本地人到另一個。

線程局部內存泄漏通常不應該是有界線程池的主要問題,因爲任何線程局部最終都可能被覆蓋;即當線程被重新使用時。但是,如果您錯誤地反覆創建新的ThreadLocal實例(而不是使用static變量來保存單例實例),則線程本地值不會被覆蓋,並會累積在每個線程的threadlocals映射中。這可能會導致嚴重泄漏。


假設你正在談論一個web應用程序的處理HTTP請求,然後一個辦法過程中使用,以避免線程局部泄漏所創建的線程局部變量/被註冊到你的webapp的ServletContext一個ServletRequestListener和落實監聽器的requestDestroyed方法來清理當前線程的線程局部變量。

請注意,在這種情況下,您還需要考慮信息從一個請求泄漏到另一個請求的可能性。

+0

感謝您的回覆。問題是我只能在完成請求後才能移除threadlocal。並且我沒有簡單的方法知道何時完成請求。即時通訊的方式是我有一個攔截器在請求的開始設置threadlocal(它是一個靜態的)。所以我重置它在每個請求的開始... – Ricardo 2010-10-06 04:17:07

+1

如果threadlocal對象是一個靜態,泄漏是更容易管理的問題;即,您只會泄漏(最多)一個實例(一個線程本地值)在線程池中的每個線程。根據線程本地值是什麼,這種泄漏可能不值得困擾。 – 2011-07-06 03:15:53

+6

這並不回答原來的問題。在我看來,問題是關於如何清理Web應用程序中的取消部署ThreadLocals以防止內存泄漏。在這種情況下使用靜態ThreadLocal是不夠的,因爲重新部署的應用程序將在另一個類加載器中。 – rustyx 2012-01-25 12:29:18

11

沒有辦法清除ThreadLocal值,除了這個線程放在第一個地方(或線程被垃圾回收 - 而不是工作線程的情況下)。這意味着當servlet請求完成時(或者在將AsyncContext傳送到Servlet 3中的另一個線程之前),應該注意清理你的ThreadLocal,因爲在那之後你可能永遠不會有機會進入特定的工作線程,因此,將在您的Web應用程序未部署而服務器未重新啓動的情況下泄漏內存。

做這種清理的好地方是ServletRequestListener.requestDestroyed()

如果你使用Spring,所有必要的佈線已經到位,你可以簡單地把東西在你的要求範圍內,而不必擔心清潔起來(即自動發生):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST); 
. . . 
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST); 
30

下面是一些代碼在沒有對實際線程局部變量的引用時清除當前線程中的所有線程局部變量。你也可以概括清理線程局部變量爲其他線程:

private void cleanThreadLocals() { 
     try { 
      // Get a reference to the thread locals table of the current thread 
      Thread thread = Thread.currentThread(); 
      Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); 
      threadLocalsField.setAccessible(true); 
      Object threadLocalTable = threadLocalsField.get(thread); 

      // Get a reference to the array holding the thread local variables inside the 
      // ThreadLocalMap of the current thread 
      Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); 
      Field tableField = threadLocalMapClass.getDeclaredField("table"); 
      tableField.setAccessible(true); 
      Object table = tableField.get(threadLocalTable); 

      // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object 
      // is a reference to the actual ThreadLocal variable 
      Field referentField = Reference.class.getDeclaredField("referent"); 
      referentField.setAccessible(true); 

      for (int i=0; i < Array.getLength(table); i++) { 
       // Each entry in the table array of ThreadLocalMap is an Entry object 
       // representing the thread local reference and its value 
       Object entry = Array.get(table, i); 
       if (entry != null) { 
        // Get a reference to the thread local object and remove it from the table 
        ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry); 
        threadLocal.remove(); 
       } 
      } 
     } catch(Exception e) { 
      // We will tolerate an exception here and just log it 
      throw new IllegalStateException(e); 
     } 
    } 
+2

有時這是唯一的方法..或不重用線程。 – vlad 2013-06-28 12:03:24

+1

這將刪除線程的每個人的全局變量,這在Java 6中佔據了我的位置。'java.util.concurrent.locks.ReentrantReadWriteLock'偶爾會拋出'IllegalMonitorStateException',因爲我們刪除了它的cachedHoldCounter,所以它試圖將它減少到0這個特殊的問題在Java 8中沒有發生,但誰知道其他ThreadLocals如何對此做出反應,如Spring連接或log4j MDC。 – Noumenon 2017-10-25 15:37:55

+0

我們是否需要檢查threadLocal值是否由此webapp的類加載器加載? ()。getClassLoader()== Thread.currentThread()。getContextClassLoader()。getClassLoader()== Thread.currentThread()。getContextClassLoader() – hurelhuyag 2018-02-20 02:40:54

0

我想貢獻我的答案,即使這個問題,即使它是舊的。我一直在困擾着同樣的問題(gson threadlocal沒有從請求線程中移除),甚至在服務器耗盡內存的情況下重新啓動服務器(這很糟糕!)。

在設置爲開發模式的java web應用程序的上下文中(在服務器每次感知到代碼發生變化時設置爲反彈,並且可能還在調試模式下運行),我很快就瞭解到threadlocals可以是真棒,有時是一個痛苦。我爲每個請求使用了threadlocal調用。在調用中。我有時也會使用gson來產生我的迴應。我會將過濾器中的「嘗試」塊中的Invocation包裝起來,並將其銷燬到「finally」塊中。

我觀察到的(我現在還沒有指標來支持這個)是,如果我對多個文件進行了更改,並且服務器在我的更改之間不斷彈跳,我會變得不耐煩並重新啓動服務器(tomcat準確而言)來自IDE。最有可能的是,我最終會遇到「內存不足」的例外。

我如何解決這個問題是在我的應用程序中包含一個ServletRequestListener實現,並且我的問題消失了。我認爲發生的事情是,在請求中間,如果服務器會彈跳好幾次,我的threadlocals沒有被清除(包括gson),所以我會得到關於threadlocals的警告以及後面的兩個或三個警告,服務器會崩潰。使用ServletResponseListener明確地關閉我的threadlocals,gson問題就消失了。

我希望這是有道理的,並給你一個如何克服threadlocal問題的想法。始終關閉他們的使用點。在ServletRequestListener中,測試每個線程本地包裝器,並且如果它仍然具有對某個對象的有效引用,則在該點處將其銷燬。

我還應該指出,將一個threadlocal作爲一個靜態變量包裝到一個類中是一種習慣。這樣,你可以保證通過在ServeltRequestListener中銷燬它,你不必擔心同一個類的其他實例在四處閒逛。

0

JVM會自動清理ThreadLocal對象內的所有無引用對象。

清理這些對象的另一種方法(比如說,這些對象可能是存在於周圍的所有線程不安全的對象)是將它們放入某個Object Holder類中,它基本上持有它,並且可以覆蓋finalize方法清理駐留在其中的對象。這又取決於垃圾收集器及其策略,它將調用finalize方法。

下面是一個代碼示例:

public class MyObjectHolder { 

    private MyObject myObject; 

    public MyObjectHolder(MyObject myObj) { 
     myObject = myObj; 
    } 

    public MyObject getMyObject() { 
     return myObject; 
    } 

    protected void finalize() throws Throwable { 
     myObject.cleanItUp(); 
    } 
} 

public class SomeOtherClass { 
    static ThreadLocal<MyObjectHolder> threadLocal = new ThreadLocal<MyObjectHolder>(); 
    . 
    . 
    . 
} 
1

重讀仔細Javadoc文檔:只要線程是

「每個線程擁有其線程局部變量副本的隱式引用活着並且ThreadLocal實例是可訪問的;在線程消失後,線程本地實例的所有副本都將進行垃圾回收(除非存在對這些副本的其他引用)。 '

沒有必要清理任何東西,泄漏的生存條件是'AND'。因此,即使在一個web容器中線程可以存活到應用程序中,只要webapp類被卸載(只有在父類加載器中加載的靜態類中的引用纔會阻止這一點,這與ThreadLocal無關,但是一般問題與靜態數據共享罐),然後不符合AND條件的第二條腿,所以線程本地副本有資格進行垃圾回收。

線程本地不能成爲內存泄漏的原因,只要實現符合文檔。