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);
}
}
7条答案
按热度按时间xeufq47z1#
@lyaffe的答案对于java6来说是最好的。这个答案使用Java8中提供的解决了一些问题。
@lyaffe的答案以前是为java6编写的
MethodHandle
变得可用。它由于反射而受到性能惩罚。如果按以下方式使用,MethodHandle
提供对字段和方法的零开销访问。@李亚夫的答案也通过了
ThreadLocalMap.table
而且容易出现错误。有一种方法ThreadLocalMap.expungeStaleEntries()
现在可以做同样的事情。下面的代码有3种初始化方法来最小化调用的成本
expungeStaleEntries()
.ohtdti5x2#
没有办法清理
ThreadLocal
值(或者当线程被垃圾收集时,而不是工作线程的情况)之外的值。这意味着您应该在servlet请求完成时(或在将asynccontext传输到Servlet3中的另一个线程之前)注意清理threadlocal,因为在这之后,您可能永远没有机会进入特定的工作线程,因此,如果在服务器未重新启动的情况下取消部署web应用,则会泄漏内存。执行此类清理的好地方是servletrequestlistener.requestdestroyed()。
如果您使用spring,那么所有必要的连接都已经就绪,您可以简单地将内容放入您的请求范围,而不用担心清理它们(这会自动发生):
iqjalb3h3#
jvm将自动清理threadlocal对象中的所有无引用对象。
清理这些对象的另一种方法(例如,这些对象可能是所有存在于其周围的线程不安全对象)是将它们放在某个对象持有者类中,该类基本上持有它,您可以重写finalize方法来清理驻留在其中的对象。同样,它取决于垃圾收集器及其策略,何时调用
finalize
方法。下面是一个代码示例:
uurity8g4#
再次仔细阅读javadoc文档:
'每个线程都有一个对其线程局部变量副本的隐式引用,只要该线程是活动的并且线程局部示例是可访问的;线程消失后,其线程本地示例的所有副本都将进行垃圾收集(除非存在对这些副本的其他引用)
不需要清理任何东西,泄漏存在“和”条件。所以即使在一个web容器中,线程仍然存在于应用程序中,只要卸载了webapp类(只有在父类加载器中加载的静态类中的beeing引用才能阻止这种情况,这与threadlocal无关,但与静态数据共享jar的一般问题有关),那么and条件的第二段就不再满足,因此线程本地副本可以作为垃圾收藏。
线程本地不能成为内存泄漏的原因,因为实现符合文档要求。
piv4azn75#
下面是一些代码,当您没有对实际线程局部变量的引用时,可以从当前线程中清除所有线程局部变量。您还可以将其概括为清理其他线程的线程局部变量:
mctunoxg6#
我愿意为这个问题提供我的答案,尽管这个问题已经很老了。我也曾被同样的问题困扰(gson threadlocal没有从请求线程中删除),甚至在服务器内存耗尽时重新启动服务器(这太浪费时间了!!)。
在设置为dev模式的javaweb应用程序的上下文中(服务器被设置为在每次检测到代码更改时都会跳出,并且可能也在调试模式下运行),我很快就了解到threadlocal可能非常棒,有时甚至是一种痛苦。我对每个请求都使用threadlocal调用。在调用内部。我有时也会使用gson来生成我的响应。我会将调用 Package 在过滤器中的“try”块中,并在“finally”块中销毁它。
我观察到(我现在还没有指标来支持这一点)如果我对几个文件进行了更改,而服务器在我的更改之间不断跳转,我会变得不耐烦,并从ide重新启动服务器(准确地说是tomcat)。最有可能的是,我最终会出现“内存不足”的异常。
我是如何在我的应用程序中包含一个servletrequestlistener实现的,我的问题就消失了。我认为发生的事情是,在一个请求的中间,如果服务器会反弹几次,我的threadlocals没有被清除(包括gson),所以我会得到这个关于threadlocals的警告,两三个警告之后,服务器就会崩溃。随着servletresponselistener显式关闭我的threadlocals,gson问题消失了。
我希望这是有意义的,并给你一个如何克服当地问题的想法。始终在使用点附近关闭。在servletrequestlistener中,测试每个threadlocal Package 器,如果它仍然有对某个对象的有效引用,则在此时销毁它。
我还应该指出,将threadlocal Package 为类中的静态变量是一种习惯。这样就可以保证,通过在serveltrequestlistener中销毁它,您不必担心同一类的其他示例会出现。
tez616oj7#
javadoc说:
“每个线程都有一个对其线程局部变量副本的隐式引用,只要线程是活动的并且线程局部示例是可访问的;线程消失后,其线程本地示例的所有副本都将进行垃圾收集(除非存在对这些副本的其他引用)。
如果您的应用程序或(如果您谈论的是请求线程)容器使用线程池,这意味着线程不会消亡。如果需要,您需要自己处理线程局部变量。唯一干净的方法就是打电话给
ThreadLocal.remove()
方法。可能有两个原因需要清除线程池中线程的线程局部变量:
防止内存(或假设的资源)泄漏,或
防止通过线程局部变量从一个请求意外泄漏到另一个请求。
线程本地内存泄漏通常不应该是有界线程池的主要问题,因为任何线程本地内存最终都可能被覆盖;i、 e.重新使用螺纹时。但是,如果您错误地创建了一个新的
ThreadLocal
示例(而不是使用static
变量来保存单个示例),线程的局部值不会被覆盖,并且会在每个线程的threadlocals
Map。这可能导致严重泄漏。假设您讨论的是在webapp处理http请求期间创建/使用的线程局部变量,那么避免线程局部泄漏的一种方法是注册
ServletRequestListener
使用你的网络应用程序ServletContext
并实现侦听器的requestDestroyed
方法清除当前线程的线程局部变量。请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。