ThreadLocal垃圾回收

mic*_*nko 15 java garbage-collection classloader

来自javadoc

只要线程处于活动状态且ThreadLocal实例可访问,每个线程都会保存对其线程局部变量副本的隐式引用; 在一个线程消失之后,它的所有线程局部实例副本都要进行垃圾收集(除非存在对这些副本的其他引用).

从那看起来,ThreadLocal变量引用的对象只有在线程死亡时才会被垃圾收集.但是如果ThreadLocal变量a不再被引用并且是垃圾收集的呢?a如果持有的线程a仍然存活,那么仅由变量引用的对象是否会被垃圾回收?

例如,以下类具有ThreadLocal变量:

public class Test {
    private static final ThreadLocal a = ...; // references object b
}
Run Code Online (Sandbox Code Playgroud)

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

Fra*_*eau 7

如果ThreadLocal收集本身因为它不再可访问(引用中有"和"),那么它的所有内容最终都会被收集,这取决于它是否也被引用到其他地方并且其他ThreadLocal操作发生在同一个线程上,从而触发删除过时的条目(参见例如replaceStaleEntry或中的expungeStaleEntry方法ThreadLocalMap).在ThreadLocal没有(强烈地)通过引用的线程,它引用在螺纹:认为ThreadLocal<T>作为一个WeakHashMap<Thread, T>.

在您的示例中,如果收集了类加载器,它也将卸载Test该类(除非您有内存泄漏),并将ThreadLocal a收集它.

  • `ThreadLocalMap` 确实存储在线程中,但它可以清除陈旧的条目(请参阅[`expungeStaleEntry` 中的第 556 行](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4728d748209d/ src/share/classes/java/lang/ThreadLocal.java#l556) 或 [`replaceStaleEntry` 中的第 532 行](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4728d748209d/src/ share/classes/java/lang/ThreadLocal.java#l532))。好的,所以严格来说,这些值不是*立即*可收集的,我会再次更新答案,但是,在正确的情况下,它们*可以*在线程死亡之前*被收集。 (2认同)

Evg*_*eev 6

ThreadLocal变量在Thread中保存

ThreadLocal.ThreadLocalMap threadLocals;
Run Code Online (Sandbox Code Playgroud)

这是ThreadLocal.set/get在当前线程的第一次调用时懒惰地初始化并保持对mapuntil Thread的存活的引用.但是,对密钥的ThreadLocalMap使用WeakReferences,以便在ThreadLocal从其他地方引用时可以删除其条目.有关ThreadLocal.ThreadLocalMap详细信息,请参阅javadoc

  • 这个答案不清楚."_所以它的条目可能被删除_"这个问题最重要的部分是:它们何时会被删除?不仅是你在这一点上不清楚,文档和整个Stack Overflow似乎都同样困惑. (3认同)

Ste*_*n C 5

由此看来,仅当线程死亡时,才会对ThreadLocal变量引用的对象进行垃圾回收。

这太过简单了。它实际上说的是两件事:

  • 线程处于活动状态(尚未终止)时,该变量的值不会被垃圾回收,并且该ThreadLocal对象很容易到达。

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

在第三种重要情况下,线程仍处于活动状态,但ThreadLocal不再牢固可访问。这不在javadoc所涵盖。因此,在这种情况下,GC行为是不确定的,并且在不同的Java实现中可能会有所不同。

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

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

如果您看一下代码,在其他情况下,过时的映射条目(带有损坏的WeakReferences也可能被删除。如果在地图的获取,设置,插入或删除操作中遇到过时的条目,则对应的值将为空。在某些情况下,该代码会执行部分扫描启发式操作,但是唯一可以保证删除所有陈旧映射条目的情况是调整哈希表的大小(增长)时。


所以...

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

我们可以说的最好的是...取决于应用程序如何管理其他线程本地线程。

因此,是的,如果您重新部署Web应用程序,则过时的线程本地映射条目可能会导致存储泄漏,除非Web容器销毁并重新创建线程池中的所有请求线程。(但是我希望一个网络容器能够做到这一点……)

  • 这是对这个问题的一个很好的解释。我想添加一个精简版的结论来消除这一点:**当不再引用 `ThreadLocal` 对象时,你不能指望垃圾收集的 `ThreadLocal` 的值。您必须调用 `ThreadLocal.remove` 或终止线程。** (2认同)