如何清理线程

lrpiutwd  于 2021-07-08  发布在  Java
关注(0)|答案(7)|浏览(329)

有人举过这样的例子吗?它们是由垃圾收集器处理的吗?我用的是Tomcat6。

xeufq47z

xeufq47z1#

@lyaffe的答案对于java6来说是最好的。这个答案使用Java8中提供的解决了一些问题。
@lyaffe的答案以前是为java6编写的 MethodHandle 变得可用。它由于反射而受到性能惩罚。如果按以下方式使用, MethodHandle 提供对字段和方法的零开销访问。
@李亚夫的答案也通过了 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);
}
ohtdti5x

ohtdti5x2#

没有办法清理 ThreadLocal 值(或者当线程被垃圾收集时,而不是工作线程的情况)之外的值。这意味着您应该在servlet请求完成时(或在将asynccontext传输到Servlet3中的另一个线程之前)注意清理threadlocal,因为在这之后,您可能永远没有机会进入特定的工作线程,因此,如果在服务器未重新启动的情况下取消部署web应用,则会泄漏内存。
执行此类清理的好地方是servletrequestlistener.requestdestroyed()。
如果您使用spring,那么所有必要的连接都已经就绪,您可以简单地将内容放入您的请求范围,而不用担心清理它们(这会自动发生):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
iqjalb3h

iqjalb3h3#

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>();
    .
    .
    .
}
uurity8g

uurity8g4#

再次仔细阅读javadoc文档:
'每个线程都有一个对其线程局部变量副本的隐式引用,只要该线程是活动的并且线程局部示例是可访问的;线程消失后,其线程本地示例的所有副本都将进行垃圾收集(除非存在对这些副本的其他引用)
不需要清理任何东西,泄漏存在“和”条件。所以即使在一个web容器中,线程仍然存在于应用程序中,只要卸载了webapp类(只有在父类加载器中加载的静态类中的beeing引用才能阻止这种情况,这与threadlocal无关,但与静态数据共享jar的一般问题有关),那么and条件的第二段就不再满足,因此线程本地副本可以作为垃圾收藏。
线程本地不能成为内存泄漏的原因,因为实现符合文档要求。

piv4azn7

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

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中销毁它,您不必担心同一类的其他示例会出现。

tez616oj

tez616oj7#

javadoc说:
“每个线程都有一个对其线程局部变量副本的隐式引用,只要线程是活动的并且线程局部示例是可访问的;线程消失后,其线程本地示例的所有副本都将进行垃圾收集(除非存在对这些副本的其他引用)。
如果您的应用程序或(如果您谈论的是请求线程)容器使用线程池,这意味着线程不会消亡。如果需要,您需要自己处理线程局部变量。唯一干净的方法就是打电话给 ThreadLocal.remove() 方法。
可能有两个原因需要清除线程池中线程的线程局部变量:
防止内存(或假设的资源)泄漏,或
防止通过线程局部变量从一个请求意外泄漏到另一个请求。
线程本地内存泄漏通常不应该是有界线程池的主要问题,因为任何线程本地内存最终都可能被覆盖;i、 e.重新使用螺纹时。但是,如果您错误地创建了一个新的 ThreadLocal 示例(而不是使用 static 变量来保存单个示例),线程的局部值不会被覆盖,并且会在每个线程的 threadlocals Map。这可能导致严重泄漏。
假设您讨论的是在webapp处理http请求期间创建/使用的线程局部变量,那么避免线程局部泄漏的一种方法是注册 ServletRequestListener 使用你的网络应用程序 ServletContext 并实现侦听器的 requestDestroyed 方法清除当前线程的线程局部变量。
请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。

相关问题