来自javadoc
只要线程是活动的并且ThreadLocal示例是可访问的,每个线程就持有对其线程局部变量副本的隐式引用;在线程离开之后,其线程本地示例的所有副本都受到垃圾收集(除非存在对这些副本的其他引用)。
由此看来,ThreadLocal变量引用的对象似乎只有在线程死亡时才会被垃圾收集。但是,如果ThreadLocal变量a
不再被引用,而是要进行垃圾收集,那该怎么办?如果持有a
的线程仍然存在,那么只有变量a
引用的对象才会进行垃圾收集吗?
例如,以下类具有ThreadLocal变量:
public class Test {
private static final ThreadLocal a = ...; // references object b
}
此类引用了某个对象,而此对象没有其他引用。然后,在上下文取消部署期间,应用程序类加载程序将成为垃圾回收的对象,但线程来自线程池,因此不会终止。对象b
是否将成为垃圾回收的对象?
6条答案
按热度按时间lkaoscv71#
ThreadLocal
对象时,不能指望ThreadLocal
的值被垃圾回收。必须调用ThreadLocal.remove
或导致线程终止(感谢@Lii)
详细答案:
由此看来,ThreadLocal变量引用的对象似乎只有在线程死亡时才被垃圾收集。
这是一种过于简单化的说法。它实际上说的是两件事:
ThreadLocal
对象是强可达的时,变量的值 * 不会 * 被垃圾收集。还有第三种重要的情况,线程仍然是活动的,但是
ThreadLocal
不再是强可达的,这不在javadoc的范围内,因此,这种情况下的GC行为是未指定的,并且在不同的Java实现中可能会有所不同。事实上,对于OpenJDK Java 6到OpenJDK Java 8(以及其他从这些代码库派生的实现),实际行为相当复杂。线程的thread-locals值保存在
ThreadLocalMap
对象中。ThreadLocalMap
是一个自定义的哈希Map,只适合维护线程本地值。[...]为了帮助处理非常大和长期的使用,哈希表条目使用WeakReferences
作为键。然而,由于不使用引用队列,只有当表开始耗尽空间时,才能保证删除过时的条目。如果你看一下代码,陈旧的Map条目(带有损坏的
WeakReferences
)* 可能 * 在其他情况下也会被删除。如果在Map上执行get、set、insert或remove操作时遇到过时的条目,则相应的值将为空。在某些情况下,代码会执行部分扫描试探法,但是我们能够 * 保证 * 所有过时的Map条目被移除的唯一情况是当散列表被调整大小(增长)时。所以...
然后,在上下文取消部署期间,应用程序类加载器成为垃圾收集的对象,但线程来自线程池,因此它不会死亡。对象
b
是否将成为垃圾收集的对象?我们能说的最好的情况是,它可能是......取决于应用程序如何管理其他线程本地线程的问题。
因此,如果您重新部署一个Web应用程序,那么过时的线程本地Map条目 * 可能 * 会导致存储泄漏,除非Web容器销毁并重新创建线程池中的所有请求线程(您可能希望Web容器能够做到这一点,但AFAIK没有指定)。
另一种选择是让您的Web应用程序的Servlet在每个请求完成(成功或失败)后调用
ThreadLocal.remove
,从而始终进行清理。vnjpjtjt2#
ThreadLocal变量保存在Thread中
它在当前线程中第一次调用
ThreadLocal.set/get
时延迟初始化,并在Thread
激活之前保持对map
的引用。但是,ThreadLocalMap
使用WeakReferences
作为键,因此当从任何其他地方引用ThreadLocal
时,可以删除其条目。有关详细信息,请参见ThreadLocal.ThreadLocalMap
javadocqpgpyjmq3#
如果
ThreadLocal
本身因为不可访问而被收集(引号中有一个“and”),那么它的所有内容最终都可以被收集,这取决于它是否也在其他地方被引用 * 以及 * 其他ThreadLocal
操作是否发生在同一线程上,触发旧条目的移除(例如参见ThreadLocalMap
中的replaceStaleEntry
或expungeStaleEntry
方法)。ThreadLocal
不是被线程 *(强)引用,它 * 引用 * 线程:把ThreadLocal<T>
看作是WeakHashMap<Thread, T>
。在您的示例中,如果类加载器被收集,它也将卸载
Test
类(除非您有内存泄漏),并且ThreadLocal
a
将被收集。yvt65v4c4#
ThreadLocal包含对保存键值对的WeakHashMap的引用
toiithl65#
这取决于,如果你引用它作为静态或单例,并且你的类没有被卸载,它不会被垃圾收集,这就是为什么在应用服务器环境中,对于
ThreadLocal
值,你必须使用一些侦听器或请求过滤器,以确保你在请求处理结束时解引用所有线程局部变量。您可以查看here以了解其他一些解释。
EDIT:在上下文中对线程池进行了询问,当然如果Thread是垃圾线程,则局部线程是垃圾线程。
muk1a3rh6#
如果对象b以某种方式引用了你的Test类,那么它就不会成为垃圾回收的对象。这可能在你不知情的情况下发生。例如,如果你有这样一段代码:
双大括号初始化{{add(5);}}将创建一个引用你的Test类的匿名类,因此即使你不再引用你的Test类,此对象也永远不会被垃圾回收。如果在Web应用中使用该Test类,则它将引用其类加载器,这将阻止对所有其他类进行GCed。
此外,如果你的B对象是一个简单的对象,它不会立即成为GC的主题。只有当Thread类中的ThreadLocal.ThreadLocalMap被调整大小时,你才能让你的b对象成为GC的主题。
然而,我为这个问题创建了一个solution,这样当你重新部署你的web应用程序时,你就永远不会有类加载器泄漏。