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);
}
}
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);
}
8条答案
按热度按时间qncylg1j1#
javadoc是这样说的:
“只要线程是活动的并且ThreadLocal示例是可访问的,每个线程就持有对其线程局部变量副本的隐式引用;当一个线程离开后,它的线程本地示例的所有副本都要进行垃圾收集(除非存在对这些副本的其他引用)。
如果你的应用程序或(如果你谈论的是请求线程)容器使用了线程池,这意味着线程不会死。如果必要,你需要自己处理线程本地变量。唯一干净的方法是调用
ThreadLocal.remove()
方法。可能有两个原因需要清除线程池中线程的线程局部变量:
线程本地内存泄漏通常不应该是绑定线程池的主要问题,因为任何线程本地内存最终都可能被覆盖;例如,当线程被重用时。但是,如果你错误地一次又一次地创建一个新的
ThreadLocal
示例(而不是使用static
变量来保存一个单一示例),线程的本地值不会被覆盖,而是会在每个线程的threadlocals
Map中累积。这可能会导致严重的泄漏。假设您正在讨论在Web应用程序处理HTTP请求期间创建/使用的线程局部变量,那么避免线程局部变量泄漏的一种方法是使用Web应用程序的
ServletContext
注册ServletRequestListener
,并实现侦听器的requestDestroyed
方法来清除当前线程的线程局部变量。请注意,在这种情况下,您还需要考虑从一个请求向另一个请求泄漏 * 信息 * 的可能性。
s4n0splo2#
下面是一些代码,用于在没有引用实际线程局部变量时,从当前线程中清除所有线程局部变量。您也可以将其推广为清除其他线程的线程局部变量:
bqujaahr3#
没有办法清除
ThreadLocal
值,除非从 * 最初将它们放入其中的线程 * 中清除(或者当线程被垃圾收集时-工作线程不是这种情况)。(或者在将AsyncContext传输到Servlet 3中的另一个线程之前),因为在这之后您可能再也没有机会进入那个特定的工作线程,因此,如果在服务器未重新启动的情况下取消部署Web应用程序,则会泄漏内存。ServletRequestListener.requestDestroyed()是进行此类清理的好地方。
如果你使用Spring,所有必要的连接都已经准备好了,你可以简单地把东西放在你的请求作用域中,而不用担心清理它们(这是自动发生的):
nxowjjhe4#
请仔细阅读Javadoc文档:
'只要线程处于活动状态并且ThreadLocal示例可访问,每个线程都会保留对其线程局部变量副本的隐式引用;在一个线程消失之后,它的线程本地示例的所有副本都要进行垃圾收集(除非存在对这些副本的其他引用)。
不需要清除任何东西,有一个'AND'条件使泄漏继续存在。因此,即使在线程继续存在于应用程序中的Web容器中,只要卸载了webapp类(只有在父类加载器中加载的静态类中引用才会防止这种情况,这与ThreadLocal无关,而是与具有静态数据的共享jar有关的一般问题)则不再满足AND条件的第二分支,因此该线程本地副本适合于垃圾收集。
只要实现符合文档要求,线程本地不可能是内存泄漏的原因。
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中销毁它,你就不必担心同一个类的其他示例在附近徘徊。
7gs2gvoe6#
@lyaffe的答案是Java 6可能的最佳答案。使用Java 8中可用的内容,该答案可以解决一些问题。
@lyaffe的答案是在
MethodHandle
可用之前为Java 6编写的。它会因为反射而遭受性能损失。如果按如下方式使用,MethodHandle
提供对字段和方法的 * 零开销 * 访问。@lyaffe的答案也会显式地通过
ThreadLocalMap.table
,很容易出现bug,现在有一个方法ThreadLocalMap.expungeStaleEntries()
可以做同样的事情。下面的代码有3个初始化方法,可以最大限度地减少调用
expungeStaleEntries()
的开销。xqk2d5yq7#
JVM将自动清除ThreadLocal对象中的所有无引用对象。
清理这些对象的另一种方法(例如,这些对象可能是周围存在的所有线程不安全对象)是将它们放在某个Object保持器类中,该类基本上保存这些对象,您可以覆盖finalize方法来清理驻留在其中的对象。同样,这取决于垃圾收集器及其策略,它何时调用
finalize
方法。下面是一个代码示例:
lmvvr0a88#