19

来自 javadoc

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

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

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

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

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

4

6 回答 6

8

ThreadLocal 变量保存在 Thread 中

ThreadLocal.ThreadLocalMap threadLocals;

ThreadLocal.set/get在当前线程中的第一次调用时延迟初始化,并持有对map直到Thread存活的引用。但是ThreadLocalMap用于WeakReferences键,因此当ThreadLocal从其他地方引用时,它的条目可能会被删除。有关详细信息,请参阅ThreadLocal.ThreadLocalMapjavadoc

于 2013-06-14T09:51:08.960 回答
7

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

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

于 2013-06-14T09:03:50.663 回答
7

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

那是过于简单化了。它实际上说的是两件事:

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

  • 当线程终止时,该值受制于正常的垃圾收集规则。

还有一种重要的第三种情况,即线程仍处于活动状态,但ThreadLocal不再强可达。javadoc 没有涵盖这些内容。因此,这种情况下的 GC 行为是未指定的,并且在不同的 Java 实现中可能会有所不同。

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

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

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


所以 ...

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

我们可以说的最好的情况是它可能是......取决于应用程序如何管理其他线程本地有问题的线程。

所以是的,如果您重新部署 web 应用程序,陈旧的线程本地映射条目可能是存储泄漏,除非 web 容器销毁并重新创建线程池中的所有请求线程。(您希望 Web 容器能够/可以做到这一点,但 AFAIK 没有指定。)

ThreadLocal.remove另一种选择是让您的 webapp 的 Servlet 始终通过在每个请求完成(成功或其他方式)时调用每个 Servlet 来自行清理。

于 2014-12-17T12:34:38.487 回答
3

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

在此处输入图像描述

于 2013-06-14T10:00:19.917 回答
0

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

你可以在这里寻找一些其他的解释。

编辑:在线程池的上下文中,当然如果线程是垃圾线程本地是。

于 2013-06-14T09:04:16.417 回答
-1

如果对象 b 以某种方式引用您的 Test 类,它将不会被垃圾收集。它可以在你无意的情况下发生。例如,如果您有这样的代码:

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 的大小时,您的对象 b 主题才会用于 GC。

但是我为这个问题创建了一个解决方案,所以当你重新部署你的 web 应用程序时,你永远不会有类加载器泄漏。

于 2016-08-09T12:58:27.900 回答