ThreadLocal垃圾回收

时间:2013-06-14 08:40:43

标签: java garbage-collection classloader

来自javadoc

  

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

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

例如,下面的类包含ThreadLocal变量:

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

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

6 个答案:

答案 0 :(得分:7)

如果收集ThreadLocal本身因为它不再可访问(引用中有“和”),那么最终可以收集其所有内容,具体取决于它是否也被引用到其他地方其他ThreadLocal操作发生在同一个线程上,触发了过时条目的删除(例如参见ThreadLocalMap中的replaceStaleEntryexpungeStaleEntry方法)。 <{1}}不是(强烈)引用线程,它引用线程:将ThreadLocal视为ThreadLocal<T>

在您的示例中,如果收集了类加载器,它也将卸载WeakHashMap<Thread, T>类(除非您有内存泄漏),并且将收集Test ThreadLocal

答案 1 :(得分:6)

ThreadLocal变量在Thread

中保存
ThreadLocal.ThreadLocalMap threadLocals;

在当前线程中的第一个ThreadLocal.set/get调用中懒惰地初始化,并保持对map的引用,直到Thread处于活动状态。但是ThreadLocalMap使用WeakReferences作为密钥,因此当从其他地方引用ThreadLocal时,可能会删除其条目。有关详细信息,请参阅ThreadLocal.ThreadLocalMap javadoc

答案 2 :(得分:4)

  从那里看来,ThreadLocal变量引用的对象似乎只在线程死亡时才被垃圾收集。

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

  • 当线程处于活动状态(尚未终止)时,变量的值将不会被收集,并且ThreadLocal对象可以很容易地访问。

  • 当线程终止时, 值将受到正常的垃圾收集规则的约束。

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

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

  

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

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


所以......

  

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

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

所以,如果重新部署webapp,过时的线程局部映射条目可能是存储泄漏,除非Web容器销毁并重新创建线程池中的所有请求线程。 (但我希望网络容器可以做到这一点......)

答案 3 :(得分:3)

ThreadLocal包含对保存键值对的WeakHashMap的引用

enter image description here

答案 4 :(得分:0)

这取决于,如果您将其引用为静态或单例并且您的类未被卸载,则不会进行垃圾回收,这就是为什么在应用程序服务器环境中并且使用ThreadLocal值时,您必须使用一些监听器或请求过滤器确保在请求处理结束时解除引用所有线程局部变量。或者使用框架的一些请求范围功能。

您可以查看here以获取其他一些解释。

编辑:在所询问的线程池的上下文中,当然如果线程是被攻击的线程本地人。

答案 5 :(得分:-1)

如果以某种方式引用您的Test类,则对象b将不会进行垃圾回收。没有你的意图就可能发生。例如,如果你有这样的代码:

public class Test {
    private static final ThreadLocal<Set<Integer>> a =
     new ThreadLocal<Set<Integer>>(){
            @Override public Set<Integer> initialValue(){
                return new HashSet<Integer>(){{add(5);}};
            }
    };
}

双括号初始化{{add(5);}}将创建一个匿名类,该类引用您的Test类,因此即使您不再引用Test类,此对象也永远不会被垃圾回收。如果在Web应用程序中使用该Test类,那么它将引用其类加载器,这将阻止所有其他类被GC。

此外,如果您的b对象是一个简单的对象,它将不会立即受GC影响。只有在调整Thread类中的ThreadLocal.ThreadLocalMap时,才会为GC提供对象b主题。

但是我为此问题创建了solution,因此当您重新部署Web应用程序时,您将永远不会有类加载器泄漏。