如何清理ThreadLocals

发布于 2024-09-26 17:50:35 字数 53 浏览 1 评论 0原文

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

Does any one have an example how to do this? Are they handled by the garbage collector? I'm using Tomcat 6.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

微凉徒眸意 2024-10-03 17:50:35

javadoc 是这样说的:

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

如果您的应用程序或(如果您正在谈论请求线程)容器使用线程池,则意味着线程不会终止(如有必要),您需要自己处理线程局部变量。执行此操作的唯一干净方法是调用 ThreadLocal.remove() 方法

您可能需要清理线程池中线程的线程局部变量有两个原因:

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

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


假设您正在讨论在 Web 应用程序处理 HTTP 请求期间创建/使用的线程局部变量,那么避免线程局部泄漏的一种方法是向 Web 应用程序的 ServletContext 注册一个 ServletRequestListener 并实现侦听器的 requestDestroyed 方法来清理当前线程的线程局部变量。

请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。

The javadoc says this:

"Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

If your application or (if you are talking about request threads) container uses a thread pool that means that threads don't die. If necessary, you would need to deal with the thread locals yourself. The only clean way to do this is to call the ThreadLocal.remove() method.

There are two reasons you might want to clean up thread locals for threads in a thread pool:

  • to prevent memory (or hypothetically resource) leaks, or
  • to prevent accidental leakage of information from one request to another via thread locals.

Thread local memory leaks should not normally be a major issue with bounded thread pools since any thread locals are likely to get overwritten eventually; i.e. when the thread is reused. However, if you make the mistake of creating a new ThreadLocal instances over and over again (instead of using a static variable to hold a singleton instance), the thread local values won't get overwritten, and will accumulate in each thread's threadlocals map. This could result in a serious leak.


Assuming that you are talking about thread locals that are created / used during a webapp's processing of an HTTP request, then one way to avoid the thread local leaks is to register a ServletRequestListener with your webapp's ServletContext and implement the listener's requestDestroyed method to cleanup the thread locals for the current thread.

Note that in this context you also need to consider the possibility of information leaking from one request to another.

谈下烟灰 2024-10-03 17:50:35

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

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

Here is some code to clean all thread local variables from the current thread when you do not have a reference to the actual thread local variable. You can also generalize it to cleanup thread local variables for other threads:

    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);
        }
    }
最近可好 2024-10-03 17:50:35

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

There is no way to cleanup ThreadLocal values except from within the thread that put them in there in the first place (or when the thread is garbage collected - not the case with worker threads). This means you should take care to clean up your ThreadLocal's when a servlet request is finished (or before transferring AsyncContext to another thread in Servlet 3), because after that point you may never get a chance to enter that specific worker thread, and hence, will leak memory in situations when your web app is undeployed while the server is not restarted.

A good place to do such cleanup is ServletRequestListener.requestDestroyed().

If you use Spring, all the necessary wiring is already in place, you can simply put stuff in your request scope without worrying about cleaning them up (that happens automatically):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
旧故 2024-10-03 17:50:35

再次仔细阅读 Javadoc 文档:

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

无需清洁任何东西,泄漏存在的条件是“与”。因此,即使在线程存活于应用程序的 Web 容器中,
只要 webapp 类被卸载(只有在父类加载器中加载的静态类中引用才会阻止这种情况,这与 ThreadLocal 无关,而是具有静态数据的共享 jar 的一般问题),然后 AND 的第二条腿条件不再满足,因此线程本地副本有资格进行垃圾回收。

就实现符合文档而言,线程本地不可能是内存泄漏的原因。

Reading again the Javadoc documentation carefully:

'Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
'

There is no need to clean anything, there is an 'AND' condition for the leak to survive. So even in a web container where thread survive to the application,
as long as the webapp class is unloaded ( only beeing reference in a static class loaded in the parent class loader would prevent this and this has nothing to do with ThreadLocal but general issues with shared jars with static data ) then the second leg of the AND condition is not met anymore so the thread local copy is eligible for garbage collection.

Thread local can't be the cause of memory leaks, as far the implementation meets the documentation.

ㄖ落Θ余辉 2024-10-03 17:50:35

我想为这个问题贡献我的答案,尽管它已经很旧了。我也被同样的问题困扰(gson threadlocal 没有从请求线程中删除),甚至在内存不足时重新启动服务器(这很糟糕!!)。

在设置为开发模式的 Java Web 应用程序的上下文中(服务器设置为每次检测到代码更改时都会弹跳,并且可能也在调试模式下运行),我很快就了解到 threadlocals 可以很棒有时会很痛苦。我对每个请求都使用了线程本地调用。里面的调用。我有时也会使用 gson 来生成我的响应。我会将调用包装在过滤器中的“try”块内,并将其销毁在“finally”块内。

我观察到(我现在没有指标来支持这一点)是,如果我对几个文件进行了更改,并且服务器在我的更改之间不断地跳动,我会不耐烦并重新启动服务器(准确地说是 tomcat)从 IDE 中。最有可能的是,我最终会遇到“内存不足”异常。

我解决这个问题的方法是在我的应用程序中包含 ServletRequestListener 实现,然后我的问题就消失了。我认为发生的情况是,在请求过程中,如果服务器会弹跳几次,我的线程局部变量不会被清除(包括 gson),所以我会收到有关线程局部变量的警告,并在稍后收到两三个警告,服务器会崩溃。随着 ServletResponseListener 显式关闭我的线程局部变量,gson 问题就消失了。

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

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

I would like to contribute my answer to this question even though it's old. I had been plagued by the same problem (gson threadlocal not getting removed from the request thread), and had even gotten comfortable restarting the server anytime it ran out of memory (which sucks big time!!).

In the context of a java web app that is set to dev mode (in that the server is set to bounce every time it senses a change in the code, and possibly also running in debug mode), I quickly learned that threadlocals can be awesome and sometime be a pain. I was using a threadlocal Invocation for every request. Inside the Invocation. I'd sometimes also use gson to generate my response. I would wrap the Invocation inside a 'try' block in the filter, and destroy it inside a 'finally' block.

What I observed (I have not metrics to back this up for now) is that if I made changes to several files and the server was constantly bouncing in between my changes, I'd get impatient and restart the server (tomcat to be precise) from the IDE. Most likely than not, I'd end up with an 'Out of memory' exception.

How I got around this was to include a ServletRequestListener implementation in my app, and my problem vanished. I think what was happening is that in the middle of a request, if the server would bounce several times, my threadlocals were not getting cleared up (gson included) so I'd get this warning about the threadlocals and two or three warning later, the server would crash. With the ServletResponseListener explicitly closing my threadlocals, the gson problem vanished.

I hope this makes sense and gives you an idea of how to overcome threadlocal issues. Always close them around their point of usage. In the ServletRequestListener, test each threadlocal wrapper, and if it still has a valid reference to some object, destroy it at that point.

I should also point out that make it a habit to wrap a threadlocal as a static variable inside a class. That way you can be guaranteed that by destroying it in the ServeltRequestListener, you won't have to worry about other instances of the same class hanging around.

落日海湾 2024-10-03 17:50:35

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

@lyaffe's answer is the best possible for Java 6. There are a few issues that this answer resolves using what is available in Java 8.

@lyaffe's answer was written for Java 6 before MethodHandle became available. It suffers from performance penalties due to reflection. If used as below, MethodHandle provides zero overhead access to fields and methods.

@lyaffe's answer also goes through the ThreadLocalMap.table explicitly and is prone to bugs. There is a method ThreadLocalMap.expungeStaleEntries() now available that does the same thing.

The code below has 3 initialization methods to minimize the cost of invoking 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);
}
筱武穆 2024-10-03 17:50:35

JVM 会自动清理 ThreadLocal 对象中的所有无引用对象。

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

The JVM would automatically clean-up all the reference-less objects that are within the ThreadLocal object.

Another way to clean up those objects (say for example, these objects could be all the thread unsafe objects that exist around) is to put them inside some Object Holder class, which basically holds it and you can override the finalize method to clean the object that reside within it. Again it depends on the Garbage Collector and its policies, when it would invoke the finalize method.

Here is a code sample:

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>();
    .
    .
    .
}
﹎☆浅夏丿初晴 2024-10-03 17:50:35
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);
}
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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文