【发布时间】:2022-06-10 18:03:13
【问题描述】:
仅在 stackoverflow 上就有几个(几十个)关于 ThreadLocals 如何导致内存泄漏的问题。我不想添加它们。但是在阅读了它们和一连串的答案之后,并且在由于保留的 ThreadLocal Map Entry 值巨大而遇到巨大的内存泄漏之后,我只是通过将 ThreadLocal<T> 的使用更改为 ThreadLocal<WeakReference<T>> 来解决这个问题,现在我想知道,为什么不总是那样做,或者更确切地说,为什么(在世界上)原始 ThreadLocal 的设计者使用 WeakReference 作为键(我想是线程)而不是值?为什么 ThreadLocal 映射上至少没有一些清理管家可以删除丢失密钥的条目?
我知道“为什么”问题有被标记和关闭的风险。但我们从问为什么中学习?可能有人知道我忽略了什么。
所以这是我建议始终使用的,而不是裸 ThreadLocal:
import java.lang.ref.WeakReference;
public class PragmaticThreadLocal<T> extends ThreadLocal<T> {
public T get() {
final WeakReference<T> ref = (WeakReference<T>)super.get();
return ref == null ? null : ref.get();
}
public void set(T value) {
super.set((T)(new WeakReference<T>(value)));
}
}
现在,如果我们走到这一步,为什么不将其视为 JDK 中的错误并在将来修复所有 ThreadLocals 对该值都有一个 WeakReference,因为永远没有任何点可以保留该值,尤其是如果对键(线程)的弱引用已经被清除了?对我来说,这看起来像一个真正的错误。因为拥有一个键弱而值强的 Map 绝对没用。一旦键被垃圾收集,就再也无法从映射中访问这些值了!
这不能解决所有已知存在于 ThreadLocals 周围的堆和 perm gen 内存泄漏问题吗?
编辑:好的,第一条评论和回答告诉我我错了。如果 Thread 消失,则不会保留 ThreadLocal。但如果 Thread 还在,那么 ThreadLocal 也会在那里。
我想我很困惑为什么没有解决一般问题。但在我自己的情况下,情况是我有一个持有 ThreadLocal 的对象,并且该对象消失了,但线程没有。然后我应该将 ThreadLocal remove() 调用放在可能消失的对象的终结器中吗?
我在没有使用任何 ThreadLocal 的情况下解决了我的问题,但我仍在其他地方使用它们,我想提出一个使用它们的标准做法。就像关闭文件一样。
当一个对象持有一个 ThreadLocal,而该对象消失时,垃圾收集器是否不会收集它持有的 ThreadLocal,然后是 ThreadLocal 映射条目也带有键和值?
【问题讨论】:
-
我认为您误解了 ThreadLocal 的工作原理。当一个线程终止时,它的 ThreadLocal 值也随之终止。但只要它还活着,为什么它有权让其所指对象被垃圾回收呢?
-
@shmosel 也许我误解了它。所以,不是错误。
标签: java memory-leaks thread-local