有人举过这样的例子吗?它们是由垃圾收集器处理的吗?我用的是Tomcat6。
xeufq47z1#
@lyaffe的答案对于java6来说是最好的。这个答案使用Java8中提供的解决了一些问题。@lyaffe的答案以前是为java6编写的 MethodHandle 变得可用。它由于反射而受到性能惩罚。如果按以下方式使用, MethodHandle 提供对字段和方法的零开销访问。@李亚夫的答案也通过了 ThreadLocalMap.table 而且容易出现错误。有一种方法 ThreadLocalMap.expungeStaleEntries() 现在可以做同样的事情。下面的代码有3种初始化方法来最小化调用的成本 expungeStaleEntries() .
MethodHandle
ThreadLocalMap.table
ThreadLocalMap.expungeStaleEntries()
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);}
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();
result = s_getThreadLocals.invoke(thread);
return(result);
private static MethodHandle initThreadLocals()
MethodHandle result;
Field field;
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);
private static MethodHandle initExpungeStaleEntries()
Class<?> clazz;
Method method;
threadLocals = getThreadLocals();
clazz = threadLocals.getClass();
method = clazz.getDeclaredMethod("expungeStaleEntries");
method.setAccessible(true);
unreflect(method);
catch (NoSuchMethodException | SecurityException | IllegalAccessException e)
ohtdti5x2#
没有办法清理 ThreadLocal 值(或者当线程被垃圾收集时,而不是工作线程的情况)之外的值。这意味着您应该在servlet请求完成时(或在将asynccontext传输到Servlet3中的另一个线程之前)注意清理threadlocal,因为在这之后,您可能永远没有机会进入特定的工作线程,因此,如果在服务器未重新启动的情况下取消部署web应用,则会泄漏内存。执行此类清理的好地方是servletrequestlistener.requestdestroyed()。如果您使用spring,那么所有必要的连接都已经就绪,您可以简单地将内容放入您的请求范围,而不用担心清理它们(这会自动发生):
ThreadLocal
RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);. . .RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
iqjalb3h3#
jvm将自动清理threadlocal对象中的所有无引用对象。清理这些对象的另一种方法(例如,这些对象可能是所有存在于其周围的线程不安全对象)是将它们放在某个对象持有者类中,该类基本上持有它,您可以重写finalize方法来清理驻留在其中的对象。同样,它取决于垃圾收集器及其策略,何时调用 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>(); . . .}
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>();
.
uurity8g4#
再次仔细阅读javadoc文档:'每个线程都有一个对其线程局部变量副本的隐式引用,只要该线程是活动的并且线程局部示例是可访问的;线程消失后,其线程本地示例的所有副本都将进行垃圾收集(除非存在对这些副本的其他引用)不需要清理任何东西,泄漏存在“和”条件。所以即使在一个web容器中,线程仍然存在于应用程序中,只要卸载了webapp类(只有在父类加载器中加载的静态类中的beeing引用才能阻止这种情况,这与threadlocal无关,但与静态数据共享jar的一般问题有关),那么and条件的第二段就不再满足,因此线程本地副本可以作为垃圾收藏。线程本地不能成为内存泄漏的原因,因为实现符合文档要求。
piv4azn75#
下面是一些代码,当您没有对实际线程局部变量的引用时,可以从当前线程中清除所有线程局部变量。您还可以将其概括为清理其他线程的线程局部变量:
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); } }
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
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 方法清除当前线程的线程局部变量。请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。
ThreadLocal.remove()
static
threadlocals
ServletRequestListener
ServletContext
requestDestroyed
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
方法清除当前线程的线程局部变量。请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。