线程本地垃圾回收

2022-09-02 13:06:19

来自 javadoc

每个线程都持有对其线程局部变量副本的隐式引用,只要该线程处于活动状态并且 ThreadLocal 实例可访问;线程消失后,其线程本地实例的所有副本都将受到垃圾回收(除非存在对这些副本的其他引用)。

由此看来,ThreadLocal 变量引用的对象似乎仅在线程死亡时才被垃圾回收。但是,如果 ThreadLocal 变量不再被引用并且受垃圾回收的影响,该怎么办?如果 hold 的线程仍处于活动状态,则仅按变量进行的对象引用是否会受到垃圾回收的影响?aaa

例如,有以下带有 ThreadLocal 变量的类:

public class Test {
    private static final ThreadLocal a = ...; // references object b
}

此类引用某些对象,而此对象没有其他引用。然后在上下文取消部署期间,应用程序类装入器成为垃圾回收的主题,但线程来自线程池,因此它不会死亡。对象是否会成为垃圾回收的对象?b


答案 1

线程本地变量保留在线程中

ThreadLocal.ThreadLocalMap threadLocals;

它在当前线程中首次调用时延迟初始化,并保持对 until 的引用处于活动状态。但是,用于键,因此当从其他地方引用时,可以删除其条目。有关详细信息,请参阅 javadocThreadLocal.set/getmapThreadThreadLocalMapWeakReferencesThreadLocalThreadLocal.ThreadLocalMap


答案 2

由此看来,ThreadLocal 变量引用的对象似乎仅在线程死亡时才被垃圾回收。

这是过度简化。它实际上说的是两件事:

  • 当线程处于活动状态(尚未终止)时,不会对变量的值进行垃圾回收,并且该对象是强可访问的。ThreadLocal

  • 当线程终止时,该值受常规垃圾回收规则的约束。

还有一个重要的第三种情况,其中线程仍然处于活动状态,但不再强烈可访问。javadoc 没有涵盖这一点。因此,在这种情况下,GC 行为是未指定的,并且在不同的 Java 实现之间可能有所不同。ThreadLocal

事实上,对于OpenJDK Java 6到OpenJDK Java 8(以及从这些代码库派生的其他实现),实际行为相当复杂。线程的线程局部变量的 valuas 保存在对象中。评论是这样说的:ThreadLocalMap

ThreadLocalMap是一个自定义哈希映射,仅适用于维护线程局部值。[...]为了帮助处理非常大且长期存在的用法,哈希表条目用于键。但是,由于不使用引用队列,因此仅当表开始空间不足时,才会保证删除过时的条目。WeakReferences

如果您查看代码,在其他情况下,过时的映射条目(带有损坏的)也可能被删除。如果在地图上的 get、set、insert 或 remove 操作中遇到过时的条目,则相应的值将为 null。在某些情况下,代码会执行部分扫描启发式操作,但是我们可以保证删除所有过时映射条目的唯一情况是调整哈希表的大小(增长)。WeakReferences


所以。。。

然后在上下文取消部署期间,应用程序类装入器成为垃圾回收的主题,但线程来自线程池,因此它不会死亡。对象是否会成为垃圾回收的对象?b

我们能说的最好的是,它可能是...取决于应用程序如何管理其他线程来定位有问题的线程。

因此,是的,如果重新部署 Web 应用,则过时的线程本地映射条目可能会造成存储泄漏,除非 Web 容器会销毁并重新创建线程池中的所有请求线程。(你会希望一个Web容器可以做到这一点,但AFAIK没有指定它。

另一种选择是让你的web应用程序的Servlet总是通过在每个请求完成(成功或以其他方式)时调用每个请求来清理自己。ThreadLocal.remove