java ThreadLocal垃圾收集

qmelpv7a  于 2023-02-07  发布在  Java
关注(0)|答案(6)|浏览(162)

来自javadoc
只要线程是活动的并且ThreadLocal示例是可访问的,每个线程就持有对其线程局部变量副本的隐式引用;在线程离开之后,其线程本地示例的所有副本都受到垃圾收集(除非存在对这些副本的其他引用)。
由此看来,ThreadLocal变量引用的对象似乎只有在线程死亡时才会被垃圾收集。但是,如果ThreadLocal变量a不再被引用,而是要进行垃圾收集,那该怎么办?如果持有a的线程仍然存在,那么只有变量a引用的对象才会进行垃圾收集吗?
例如,以下类具有ThreadLocal变量:

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

此类引用了某个对象,而此对象没有其他引用。然后,在上下文取消部署期间,应用程序类加载程序将成为垃圾回收的对象,但线程来自线程池,因此不会终止。对象b是否将成为垃圾回收的对象?

lkaoscv7

lkaoscv71#

    • TL; DR**:当不再引用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,从而始终进行清理。

vnjpjtjt

vnjpjtjt2#

ThreadLocal变量保存在Thread中

ThreadLocal.ThreadLocalMap threadLocals;

它在当前线程中第一次调用ThreadLocal.set/get时延迟初始化,并在Thread激活之前保持对map的引用。但是,ThreadLocalMap使用WeakReferences作为键,因此当从任何其他地方引用ThreadLocal时,可以删除其条目。有关详细信息,请参见ThreadLocal.ThreadLocalMap javadoc

qpgpyjmq

qpgpyjmq3#

如果ThreadLocal本身因为不可访问而被收集(引号中有一个“and”),那么它的所有内容最终都可以被收集,这取决于它是否也在其他地方被引用 * 以及 * 其他ThreadLocal操作是否发生在同一线程上,触发旧条目的移除(例如参见ThreadLocalMap中的replaceStaleEntryexpungeStaleEntry方法)。ThreadLocal不是被线程 *(强)引用,它 * 引用 * 线程:把ThreadLocal<T>看作是WeakHashMap<Thread, T>
在您的示例中,如果类加载器被收集,它也将卸载Test类(除非您有内存泄漏),并且ThreadLocala将被收集。

yvt65v4c

yvt65v4c4#

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

toiithl6

toiithl65#

这取决于,如果你引用它作为静态或单例,并且你的类没有被卸载,它不会被垃圾收集,这就是为什么在应用服务器环境中,对于ThreadLocal值,你必须使用一些侦听器或请求过滤器,以确保你在请求处理结束时解引用所有线程局部变量。
您可以查看here以了解其他一些解释。
EDIT:在上下文中对线程池进行了询问,当然如果Thread是垃圾线程,则局部线程是垃圾线程。

muk1a3rh

muk1a3rh6#

如果对象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类,则它将引用其类加载器,这将阻止对所有其他类进行GCed。
此外,如果你的B对象是一个简单的对象,它不会立即成为GC的主题。只有当Thread类中的ThreadLocal.ThreadLocalMap被调整大小时,你才能让你的b对象成为GC的主题。
然而,我为这个问题创建了一个solution,这样当你重新部署你的web应用程序时,你就永远不会有类加载器泄漏。

相关问题