如何清理ThreadLocals

时间:2010-10-06 01:57:29

标签: java tomcat thread-local

有人有一个例子如何做到这一点?它们是由垃圾收集器处理的吗?我正在使用Tomcat 6。

8 个答案:

答案 0 :(得分:65)

javadoc说:

  

“每个线程都拥有对其线程局部变量副本的隐式引用,只要该线程处于活动状态且ThreadLocal实例可访问;在线程消失后,其所有线程局部实例副本都是主题垃圾收集(除非存在对这些副本的其他引用)。

如果您的应用程序或(如果您正在讨论请求线程)容器使用线程池,这意味着线程不会死亡。如有必要,您需要自己处理本地线程。唯一干净的方法是调用ThreadLocal.remove()方法。

您可能希望清除线程池中线程的线程局部因素有两个原因:

  • 防止内存(或假设资源)泄漏,或
  • 以防止通过线程本地人将信息从一个请求意外泄漏到另一个请求。

线程本地内存泄漏通常不应该是有界线程池的主要问题,因为任何线程本地都可能最终被覆盖;即当线程被重用时。但是,如果您错误地反复创建新的ThreadLocal实例(而不是使用static变量来保存单例实例),则线程本地值将不会被覆盖,并且将累积在每个线程的threadlocals地图中。这可能会导致严重的泄漏。


假设您正在讨论在webapp处理HTTP请求期间创建/使用的线程本地,那么避免线程本地泄漏的一种方法是使用您的webapp {{1}注册ServletRequestListener并实现侦听器的ServletContext方法来清理当前线程的线程本地。

请注意,在此上下文中,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。

答案 1 :(得分:33)

当您没有对实际线程局部变量的引用时,这是从当前线程清除所有线程局部变量的一些代码。您还可以将其概括为清理其他线程的线程局部变量:

    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 :(得分:16)

没有办法清除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);

答案 3 :(得分:1)

仔细阅读Javadoc文档:

&#39;每个线程都拥有对其线程局部变量副本的隐式引用,只要线程处于活动状态且ThreadLocal实例可访问;在一个线程消失之后,它的所有线程局部实例副本都要进行垃圾收集(除非存在对这些副本的其他引用)。 &#39;

没有必要清理任何东西,有一个&#39; AND&#39;泄漏的条件是生存。因此,即使在线程存活到应用程序的Web容器中, 只要webapp类被卸载(只有在父类加载器中加载的静态类中的引用将阻止这一点,这与ThreadLocal无关,但是与静态数据共享jar的一般问题)然后是AND的第二站条件不再符合,因此线程本地副本有资格进行垃圾回收。

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

答案 4 :(得分:0)

我想为这个问题贡献我的答案,即使它已经过时了。我一直受到同样的问题的困扰(gson threadlocal没有从请求线程中删除),甚至在内存不足的情况下重新启动服务器也很舒服(这很糟糕!)。

在设置为开发模式的java web应用程序的上下文中(因为服务器设置为每当它感知到代码中的更改时都会弹跳,并且可能还在调试模式下运行),我很快就学会了threadlocals可能很棒,有时候会很痛苦。我为每个请求使用了一个threadlocal调用。在调用内部。我有时也会用gson来生成我的回复。我将Invocation包装在过滤器中的'try'块中,并在'finally'块中销毁它。

我观察到的(我现在还没有指标),如果我对几个文件进行了更改,并且服务器在我的更改之间不断弹跳,我会不耐烦并重新启动服务器(tomcat到确切地说,来自IDE。最有可能的是,我最终会遇到“内存不足”的例外情况。

我如何解决这个问题是在我的应用程序中包含一个ServletRequestListener实现,我的问题就消失了。我认为发生的事情是,在请求的中间,如果服务器会多次弹跳,我的threadlocals没有被清除(包括gson)所以我会收到关于threadlocals的警告以及稍后两三个警告,服务器会崩溃。随着ServletResponseListener显式关闭我的threadlocals,gson问题消失了。

我希望这是有道理的,让您了解如何克服线程局问题。始终在使用点附近关闭它们。在ServletRequestListener中,测试每个threadlocal包装器,如果它仍然具有对某个对象的有效引用,则在那时将其销毁。

我还应该指出,养成将threadlocal包装为类中的静态变量的习惯。通过这种方式,您可以保证通过在ServeltRequestListener中销毁它,您不必担心同一类的其他实例会挂起。

答案 5 :(得分: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>();
    .
    .
    .
}

答案 6 :(得分:0)

@lyaffe的答案是Java 6的最佳选择。使用Java 8中的可用功能可以解决此问题。

@lyaffe的答案是在MethodHandle可用之前为Java 6编写的。由于反射,它遭受性能损失。如果按以下方式使用,MethodHandle将提供对字段和方法的零开销访问。

@lyaffe的答案也明确地通过了ThreadLocalMap.table,并且容易出错。现在有一种方法ThreadLocalMap.expungeStaleEntries()可做相同的事情。

下面的代码具有3种初始化方法,以最小化调用expungeStaleEntries()的成本。

private static final MethodHandle        s_getThreadLocals     = initThreadLocals();
private static final MethodHandle        s_expungeStaleEntries = initExpungeStaleEntries();
private static final ThreadLocal<Object> s_threadLocals        = ThreadLocal.withInitial(() -> getThreadLocals());

public static void expungeThreadLocalMap()
{
   Object threadLocals;

   threadLocals = s_threadLocals.get();

   try
   {
      s_expungeStaleEntries.invoke(threadLocals);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }
}

private static Object getThreadLocals()
{
   ThreadLocal<Object> local;
   Object result;
   Thread thread;

   local = new ThreadLocal<>();

   local.set(local);   // Force ThreadLocal to initialize Thread.threadLocals

   thread = Thread.currentThread();

   try
   {
      result = s_getThreadLocals.invoke(thread);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }

   return(result);
}

private static MethodHandle initThreadLocals()
{
   MethodHandle result;
   Field field;

   try
   {
      field = Thread.class.getDeclaredField("threadLocals");

      field.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflectGetter(field);

      result = Preconditions.verifyNotNull(result, "result is null");
   }
   catch (NoSuchFieldException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}

private static MethodHandle initExpungeStaleEntries()
{
   MethodHandle result;
   Class<?> clazz;
   Method method;
   Object threadLocals;

   threadLocals = getThreadLocals();
   clazz        = threadLocals.getClass();

   try
   {
      method = clazz.getDeclaredMethod("expungeStaleEntries");

      method.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflect(method);
   }
   catch (NoSuchMethodException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}

答案 7 :(得分:0)

有没有人试过用这个来清理导致内存泄漏的 ThreadLocal 实例。 github.com/codesinthedark/ImprovedThreadLocal 这是由上面的用户 1944408 分享的。