2010-10-06 46 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条件的第二条腿,所以线程本地副本有资格进行垃圾回收。

线程本地不能成为内存泄漏的原因,只要实现符合文档。