有人有一个例子如何做到这一点?它们是由垃圾收集器处理的吗?我正在使用Tomcat 6。
答案 0 :(得分:65)
javadoc说:
“每个线程都拥有对其线程局部变量副本的隐式引用,只要该线程处于活动状态且ThreadLocal实例可访问;在线程消失后,其所有线程局部实例副本都是主题垃圾收集(除非存在对这些副本的其他引用)。
如果您的应用程序或(如果您正在讨论请求线程)容器使用线程池,这意味着线程不会死亡。如有必要,您需要自己处理本地线程。唯一干净的方法是调用ThreadLocal.remove()
方法。
您可能希望清除线程池中线程的线程局部因素有两个原因:
线程本地内存泄漏通常不应该是有界线程池的主要问题,因为任何线程本地都可能最终被覆盖;即当线程被重用时。但是,如果您错误地反复创建新的ThreadLocal
实例(而不是使用static
变量来保存单例实例),则线程本地值将不会被覆盖,并且将累积在每个线程的threadlocals
地图中。这可能会导致严重的泄漏。
假设您正在讨论在webapp处理HTTP请求期间创建/使用的线程本地,那么避免线程本地泄漏的一种方法是使用您的webapp {{1}注册ServletRequestListener
并实现侦听器的ServletContext
方法来清理当前线程的线程本地。
请注意,在此上下文中,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。
答案 1 :(得分:33)
当您没有对实际线程局部变量的引用时,这是从当前线程清除所有线程局部变量的一些代码。您还可以将其概括为清理其他线程的线程局部变量:
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);
}
}
答案 2 :(得分:16)
没有办法清除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);
答案 3 :(得分:1)
仔细阅读Javadoc文档:
&#39;每个线程都拥有对其线程局部变量副本的隐式引用,只要线程处于活动状态且ThreadLocal实例可访问;在一个线程消失之后,它的所有线程局部实例副本都要进行垃圾收集(除非存在对这些副本的其他引用)。 &#39;
没有必要清理任何东西,有一个&#39; AND&#39;泄漏的条件是生存。因此,即使在线程存活到应用程序的Web容器中, 只要webapp类被卸载(只有在父类加载器中加载的静态类中的引用将阻止这一点,这与ThreadLocal无关,但是与静态数据共享jar的一般问题)然后是AND的第二站条件不再符合,因此线程本地副本有资格进行垃圾回收。
本地线程无法成为内存泄漏的原因,只要实现符合文档。
答案 4 :(得分:0)
我想为这个问题贡献我的答案,即使它已经过时了。我一直受到同样的问题的困扰(gson threadlocal没有从请求线程中删除),甚至在内存不足的情况下重新启动服务器也很舒服(这很糟糕!)。
在设置为开发模式的java web应用程序的上下文中(因为服务器设置为每当它感知到代码中的更改时都会弹跳,并且可能还在调试模式下运行),我很快就学会了threadlocals可能很棒,有时候会很痛苦。我为每个请求使用了一个threadlocal调用。在调用内部。我有时也会用gson来生成我的回复。我将Invocation包装在过滤器中的'try'块中,并在'finally'块中销毁它。
我观察到的(我现在还没有指标),如果我对几个文件进行了更改,并且服务器在我的更改之间不断弹跳,我会不耐烦并重新启动服务器(tomcat到确切地说,来自IDE。最有可能的是,我最终会遇到“内存不足”的例外情况。
我如何解决这个问题是在我的应用程序中包含一个ServletRequestListener实现,我的问题就消失了。我认为发生的事情是,在请求的中间,如果服务器会多次弹跳,我的threadlocals没有被清除(包括gson)所以我会收到关于threadlocals的警告以及稍后两三个警告,服务器会崩溃。随着ServletResponseListener显式关闭我的threadlocals,gson问题消失了。
我希望这是有道理的,让您了解如何克服线程局问题。始终在使用点附近关闭它们。在ServletRequestListener中,测试每个threadlocal包装器,如果它仍然具有对某个对象的有效引用,则在那时将其销毁。
我还应该指出,养成将threadlocal包装为类中的静态变量的习惯。通过这种方式,您可以保证通过在ServeltRequestListener中销毁它,您不必担心同一类的其他实例会挂起。
答案 5 :(得分:0)
JVM会自动清理ThreadLocal对象中的所有无引用对象。
另一种清理这些对象的方法(例如,这些对象可能是周围存在的所有线程不安全对象)是将它们放在一个Object Holder类中,它基本上保存它,你可以覆盖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>();
.
.
.
}
答案 6 :(得分:0)
@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);
}
答案 7 :(得分:0)
有没有人试过用这个来清理导致内存泄漏的 ThreadLocal 实例。 github.com/codesinthedark/ImprovedThreadLocal 这是由上面的用户 1944408 分享的。