【问题标题】:ThreadLocal and memory leak -- why not always use ThreadLocal<WeakReference<T>> instead of ThreadLocal<T>?ThreadLocal 和内存泄漏——为什么不总是使用 ThreadLocal<WeakReference<T>> 而不是 ThreadLocal<T>?
【发布时间】:2022-06-10 18:03:13
【问题描述】:

仅在 stackoverflow 上就有几个(几十个)关于 ThreadLocals 如何导致内存泄漏的问题。我不想添加它们。但是在阅读了它们和一连串的答案之后,并且在由于保留的 ThreadLocal Map Entry 值巨大而遇到巨大的内存泄漏之后,我只是通过将 ThreadLocal&lt;T&gt; 的使用更改为 ThreadLocal&lt;WeakReference&lt;T&gt;&gt; 来解决这个问题,现在我想知道,为什么不总是那样做,或者更确切地说,为什么(在世界上)原始 ThreadLocal 的设计者使用 Wea​​kReference 作为键(我想是线程)而不是值?为什么 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


【解决方案1】:

如果该值始终是 WeakReference,那么在许多典型用例中,它会立即符合 GC 条件,因为它是对该值的唯一引用 - 这有什么用处?

参见ThreadLocal的JavaDocs中的示例:

    import java.util.concurrent.atomic.AtomicInteger;

    public class ThreadId {
         // Atomic integer containing the next thread ID to be assigned
         private static final AtomicInteger nextId = new AtomicInteger(0);
    
         // Thread local variable containing each thread's ID
         private static final ThreadLocal<Integer> threadId =
             new ThreadLocal<Integer>() {
                 @Override protected Integer initialValue() {
                     return nextId.getAndIncrement();
             }
         };
    
         // Returns the current thread's unique ID, assigning it if necessary
         public static int get() {
             return threadId.get();
         }
     } 

这将不再适用于您的方法。它可能每次都以不确定的方式返回一个新值,具体取决于 GC。

【讨论】:

  • 没错,呃。我知道我可能忽略了一些东西。所以我们需要一个ConditionallyWeakReference,它将持有一个对线程的WeakReference条件,并且GC会检查线程是否仍然存在,而不是收集ConditionallyWeakReference。但是,当我们指向的东西消失时,我们也会被清除。怎么可能做到这一点?
  • 我想不出任何方法可以在 Java 中实现类似的东西。我想它需要特殊的 JVM/GC 支持,因为这不能用引用语义来表达。如果您不想将引用对象的生命周期与线程耦合,则更简单的方法是不使用 ThreadLocal。 “只是”根据需要传递引用,或使用具有适当生命周期范围的 DI 容器。
猜你喜欢
  • 2011-11-25
  • 2013-07-31
  • 1970-01-01
  • 1970-01-01
  • 2011-08-11
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
相关资源
最近更新 更多