如何清理线程

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

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

xeufq47z

xeufq47z1#

@lyaffe的答案对于java6来说是最好的。这个答案使用Java8中提供的解决了一些问题。
@lyaffe的答案以前是为java6编写的 MethodHandle 变得可用。它由于反射而受到性能惩罚。如果按以下方式使用, MethodHandle 提供对字段和方法的零开销访问。
@李亚夫的答案也通过了 ThreadLocalMap.table 而且容易出现错误。有一种方法 ThreadLocalMap.expungeStaleEntries() 现在可以做同样的事情。
下面的代码有3种初始化方法来最小化调用的成本 expungeStaleEntries() .

  1. private static final MethodHandle s_getThreadLocals = initThreadLocals();
  2. private static final MethodHandle s_expungeStaleEntries = initExpungeStaleEntries();
  3. private static final ThreadLocal<Object> s_threadLocals = ThreadLocal.withInitial(() -> getThreadLocals());
  4. public static void expungeThreadLocalMap()
  5. {
  6. Object threadLocals;
  7. threadLocals = s_threadLocals.get();
  8. try
  9. {
  10. s_expungeStaleEntries.invoke(threadLocals);
  11. }
  12. catch (Throwable e)
  13. {
  14. throw new IllegalStateException(e);
  15. }
  16. }
  17. private static Object getThreadLocals()
  18. {
  19. ThreadLocal<Object> local;
  20. Object result;
  21. Thread thread;
  22. local = new ThreadLocal<>();
  23. local.set(local); // Force ThreadLocal to initialize Thread.threadLocals
  24. thread = Thread.currentThread();
  25. try
  26. {
  27. result = s_getThreadLocals.invoke(thread);
  28. }
  29. catch (Throwable e)
  30. {
  31. throw new IllegalStateException(e);
  32. }
  33. return(result);
  34. }
  35. private static MethodHandle initThreadLocals()
  36. {
  37. MethodHandle result;
  38. Field field;
  39. try
  40. {
  41. field = Thread.class.getDeclaredField("threadLocals");
  42. field.setAccessible(true);
  43. result = MethodHandles.
  44. lookup().
  45. unreflectGetter(field);
  46. result = Preconditions.verifyNotNull(result, "result is null");
  47. }
  48. catch (NoSuchFieldException | SecurityException | IllegalAccessException e)
  49. {
  50. throw new ExceptionInInitializerError(e);
  51. }
  52. return(result);
  53. }
  54. private static MethodHandle initExpungeStaleEntries()
  55. {
  56. MethodHandle result;
  57. Class<?> clazz;
  58. Method method;
  59. Object threadLocals;
  60. threadLocals = getThreadLocals();
  61. clazz = threadLocals.getClass();
  62. try
  63. {
  64. method = clazz.getDeclaredMethod("expungeStaleEntries");
  65. method.setAccessible(true);
  66. result = MethodHandles.
  67. lookup().
  68. unreflect(method);
  69. }
  70. catch (NoSuchMethodException | SecurityException | IllegalAccessException e)
  71. {
  72. throw new ExceptionInInitializerError(e);
  73. }
  74. return(result);
  75. }
展开查看全部
ohtdti5x

ohtdti5x2#

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

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

iqjalb3h3#

jvm将自动清理threadlocal对象中的所有无引用对象。
清理这些对象的另一种方法(例如,这些对象可能是所有存在于其周围的线程不安全对象)是将它们放在某个对象持有者类中,该类基本上持有它,您可以重写finalize方法来清理驻留在其中的对象。同样,它取决于垃圾收集器及其策略,何时调用 finalize 方法。
下面是一个代码示例:

  1. public class MyObjectHolder {
  2. private MyObject myObject;
  3. public MyObjectHolder(MyObject myObj) {
  4. myObject = myObj;
  5. }
  6. public MyObject getMyObject() {
  7. return myObject;
  8. }
  9. protected void finalize() throws Throwable {
  10. myObject.cleanItUp();
  11. }
  12. }
  13. public class SomeOtherClass {
  14. static ThreadLocal<MyObjectHolder> threadLocal = new ThreadLocal<MyObjectHolder>();
  15. .
  16. .
  17. .
  18. }
展开查看全部
uurity8g

uurity8g4#

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

piv4azn7

piv4azn75#

下面是一些代码,当您没有对实际线程局部变量的引用时,可以从当前线程中清除所有线程局部变量。您还可以将其概括为清理其他线程的线程局部变量:

  1. private void cleanThreadLocals() {
  2. try {
  3. // Get a reference to the thread locals table of the current thread
  4. Thread thread = Thread.currentThread();
  5. Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
  6. threadLocalsField.setAccessible(true);
  7. Object threadLocalTable = threadLocalsField.get(thread);
  8. // Get a reference to the array holding the thread local variables inside the
  9. // ThreadLocalMap of the current thread
  10. Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
  11. Field tableField = threadLocalMapClass.getDeclaredField("table");
  12. tableField.setAccessible(true);
  13. Object table = tableField.get(threadLocalTable);
  14. // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
  15. // is a reference to the actual ThreadLocal variable
  16. Field referentField = Reference.class.getDeclaredField("referent");
  17. referentField.setAccessible(true);
  18. for (int i=0; i < Array.getLength(table); i++) {
  19. // Each entry in the table array of ThreadLocalMap is an Entry object
  20. // representing the thread local reference and its value
  21. Object entry = Array.get(table, i);
  22. if (entry != null) {
  23. // Get a reference to the thread local object and remove it from the table
  24. ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
  25. threadLocal.remove();
  26. }
  27. }
  28. } catch(Exception e) {
  29. // We will tolerate an exception here and just log it
  30. throw new IllegalStateException(e);
  31. }
  32. }
展开查看全部
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 方法清除当前线程的线程局部变量。
请注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。

相关问题