tomcat 如何清除ThreadLocal

kdfy810k  于 2022-11-13  发布在  其他
关注(0)|答案(8)|浏览(401)

有没有人举一个例子来说明如何做到这一点?它们是由垃圾收集器处理的吗?我用的是Tomcat 6。

qncylg1j

qncylg1j1#

javadoc是这样说的:
“只要线程是活动的并且ThreadLocal示例是可访问的,每个线程就持有对其线程局部变量副本的隐式引用;当一个线程离开后,它的线程本地示例的所有副本都要进行垃圾收集(除非存在对这些副本的其他引用)。
如果你的应用程序或(如果你谈论的是请求线程)容器使用了线程池,这意味着线程不会死。如果必要,你需要自己处理线程本地变量。唯一干净的方法是调用ThreadLocal.remove()方法。
可能有两个原因需要清除线程池中线程的线程局部变量:

  • 以防止内存(或假设的资源)泄漏,或者
  • 以防止通过线程局部变量从一个请求到另一个请求的信息意外泄漏。

线程本地内存泄漏通常不应该是绑定线程池的主要问题,因为任何线程本地内存最终都可能被覆盖;例如,当线程被重用时。但是,如果你错误地一次又一次地创建一个新的ThreadLocal示例(而不是使用static变量来保存一个单一示例),线程的本地值不会被覆盖,而是会在每个线程的threadlocalsMap中累积。这可能会导致严重的泄漏。
假设您正在讨论在Web应用程序处理HTTP请求期间创建/使用的线程局部变量,那么避免线程局部变量泄漏的一种方法是使用Web应用程序的ServletContext注册ServletRequestListener,并实现侦听器的requestDestroyed方法来清除当前线程的线程局部变量。
请注意,在这种情况下,您还需要考虑从一个请求向另一个请求泄漏 * 信息 * 的可能性。

s4n0splo

s4n0splo2#

下面是一些代码,用于在没有引用实际线程局部变量时,从当前线程中清除所有线程局部变量。您也可以将其推广为清除其他线程的线程局部变量:

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);
        }
    }
bqujaahr

bqujaahr3#

没有办法清除ThreadLocal值,除非从 * 最初将它们放入其中的线程 * 中清除(或者当线程被垃圾收集时-工作线程不是这种情况)。(或者在将AsyncContext传输到Servlet 3中的另一个线程之前),因为在这之后您可能再也没有机会进入那个特定的工作线程,因此,如果在服务器未重新启动的情况下取消部署Web应用程序,则会泄漏内存。
ServletRequestListener.requestDestroyed()是进行此类清理的好地方。
如果你使用Spring,所有必要的连接都已经准备好了,你可以简单地把东西放在你的请求作用域中,而不用担心清理它们(这是自动发生的):

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

nxowjjhe4#

请仔细阅读Javadoc文档:
'只要线程处于活动状态并且ThreadLocal示例可访问,每个线程都会保留对其线程局部变量副本的隐式引用;在一个线程消失之后,它的线程本地示例的所有副本都要进行垃圾收集(除非存在对这些副本的其他引用)。
不需要清除任何东西,有一个'AND'条件使泄漏继续存在。因此,即使在线程继续存在于应用程序中的Web容器中,只要卸载了webapp类(只有在父类加载器中加载的静态类中引用才会防止这种情况,这与ThreadLocal无关,而是与具有静态数据的共享jar有关的一般问题)则不再满足AND条件的第二分支,因此该线程本地副本适合于垃圾收集。
只要实现符合文档要求,线程本地不可能是内存泄漏的原因。

lf5gs5x2

lf5gs5x25#

虽然这个问题已经很老了,但我还是想回答这个问题。我也曾被同样的问题困扰过(gson threadlocal没有从请求线程中删除),甚至在服务器内存耗尽时重新启动服务器都很舒服(这太糟糕了!!)。
在设置为开发模式的Java Web应用程序的上下文中(因为服务器被设置为每当它感测到代码中的变化时就弹回,并且还可能运行在调试模式中),我很快就了解到threadlocal可能很棒,有时候也很麻烦。我对每个请求都使用threadlocal Invocation。在Invocation内部,我有时候也使用gson来生成我的响应。我会将Invocation Package 在过滤器的“try”块中,并在“finally”块中销毁它。
我观察到的(目前我还没有度量标准来支持这一点)是,如果我对几个文件进行了更改,而服务器在我的更改之间不断地跳动,我会变得不耐烦,并从IDE中重新启动服务器(准确地说是tomcat)。最有可能的是,我最终会出现一个“内存不足”异常。
我解决这个问题的方法是在我的应用程序中包含一个ServletRequestListener实现,我的问题就消失了。我认为发生的情况是,在一个请求的中间,如果服务器会反弹几次,我的threadlocal就不会被清除(包括gson)所以我会收到关于threadlocals的警告,之后还会收到两三个警告,服务器将崩溃。随着ServletResponseListener显式关闭我的threadlocal,gson问题消失了。
我希望这是有意义的,并给你一个如何克服threadlocal问题的想法。总是在它们的使用点附近关闭它们。在ServletRequestListener中,测试每个threadlocal Package 器,如果它仍然有一个对某个对象的有效引用,那么就在那个点销毁它。
我还应该指出,把threadlocal Package 成类中的一个静态变量是一种习惯。这样,你就可以保证,通过在ServeltRequestListener中销毁它,你就不必担心同一个类的其他示例在附近徘徊。

7gs2gvoe

7gs2gvoe6#

@lyaffe的答案是Java 6可能的最佳答案。使用Java 8中可用的内容,该答案可以解决一些问题。
@lyaffe的答案是在MethodHandle可用之前为Java 6编写的。它会因为反射而遭受性能损失。如果按如下方式使用,MethodHandle提供对字段和方法的 * 零开销 * 访问。
@lyaffe的答案也会显式地通过ThreadLocalMap.table,很容易出现bug,现在有一个方法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);
}
xqk2d5yq

xqk2d5yq7#

JVM将自动清除ThreadLocal对象中的所有无引用对象。
清理这些对象的另一种方法(例如,这些对象可能是周围存在的所有线程不安全对象)是将它们放在某个Object保持器类中,该类基本上保存这些对象,您可以覆盖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>();
    .
    .
    .
}
lmvvr0a8

lmvvr0a88#

final ThreadLocal<T> old = backend;

// try to clean by reflect
try {
    // BGN copy from apache ThreadUtils#getAllThreads
    ThreadGroup systemGroup = Thread.currentThread().getThreadGroup();
    while (systemGroup.getParent() != null) {
        systemGroup = systemGroup.getParent();
    }
    int count = systemGroup.activeCount();
    Thread[] threads;
    do {
        threads = new Thread[count + (count / 2) + 1]; //slightly grow the array size
        count = systemGroup.enumerate(threads, true);
        //return value of enumerate() must be strictly less than the array size according to javadoc
    } while (count >= threads.length);
    // END

    // remove by reflect
    final Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
    threadLocalsField.setAccessible(true);

    Class<?> threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
    Method removeMethod = threadLocalMapClass.getDeclaredMethod("remove", ThreadLocal.class);
    removeMethod.setAccessible(true);

    for (int i = 0; i < count; i++) {
        final Object threadLocalMap = threadLocalsField.get(threads[i]);
        if (threadLocalMap != null) {
            removeMethod.invoke(threadLocalMap, old);
        }
    }

}
catch (Exception e) {
    throw new ThreadLocalAttention(e);
}

相关问题