ThreadLocal资源泄漏和WeakReference

时间:2009-06-02 16:29:00

标签: java thread-local weak-references

我对ThreadLocal的有限理解是它有resource leak issues。我收集这个问题可以通过在ThreadLocal中正确使用WeakReferences来解决(尽管我可能误解了这一点。)我只是想要一个模式或示例来正确使用带有WeakReference的ThreadLocal(如果存在)。例如,在这段代码片段中会引入WeakReference吗?

static class DateTimeFormatter {
    private static final ThreadLocal<SimpleDateFormat> DATE_PARSER_THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy/MM/dd HH:mmz");
        }
    };
    public String format(final Date date) {
        return DATE_PARSER_THREAD_LOCAL.get().format(date);
    }
    public Date parse(final String date) throws ParseException
    {
      return DATE_PARSER_THREAD_LOCAL.get().parse(date);
    }
}

8 个答案:

答案 0 :(得分:31)

ThreadLocal在内部使用WeakReference。如果ThreadLocal未被强引用,则它将被垃圾收集,即使各种线程具有通过ThreadLocal存储的值。

此外,ThreadLocal值实际存储在Thread;如果线程死亡,则收集与该线程通过ThreadLocal相关联的所有值。

如果你有一个ThreadLocal作为最终的类成员,那么这是一个强大的引用,并且在卸载该类之前无法收集它。但这就是任何类成员的工作方式,并且不被视为内存泄漏。


更新:只有当ThreadLocal中存储的值强烈引用ThreadLocal - 一种循环引用时,引用的问题才会发挥作用。

在这种情况下,值(a SimpleDateFormat)没有向ThreadLocal的向后引用。此代码中没有内存泄漏。

答案 1 :(得分:10)

我猜你正在跳过这些箍,因为SimpleDateFormat 不是线程安全的

虽然我知道我上面没有解决您的问题,但我建议您查看Joda的日期/时间工作吗? Joda有一个线程安全的日期/时间格式化机制。您也不会浪费时间学习Joda API,因为它是新标准日期/时间API提案的基础。

答案 2 :(得分:3)

不应该出现这样的问题。

线程的ThreadLocal引用被定义为只有相应的线程处于活动状态时才存在(参见javadoc) - 或者换一种方式,一旦线程不活动,如果ThreadLocal是对该对象的唯一引用,那么该对象就有资格进行垃圾收集。

所以要么你发现了一个真正的错误,应该报告它,或者你做错了什么!

答案 3 :(得分:2)

我意识到这不是对您的问题的严格答案,但作为一般规则,我不会建议在请求结束时没有明确拆除时使用ThreadLocal in situration /相互作用。经典就是在servlet容器中做这种事情,乍一看它看起来很好,但是由于线程被合并,即使在处理完每个请求之后,ThreadLocal仍会挂在资源上。< / p>

建议:

  1. 为每次互动使用类似包装器的过滤器,以在每次互动结束时清除ThreadLocal
  2. 您可以使用SimpleDateFormat的替代方案,如FastDateFormat from commons-lang或Joda,因为有人已经建议
  3. 每次需要时都可以创建一个新的SimpleDateFormat。我知道这看起来很浪费,但在大多数应用程序中你都不会注意到差异

答案 4 :(得分:1)

只需添加@Neil Coffey所说的内容,只要你的ThreadLocal实例是静态的,这应该不是问题。因为你一直在静态实例上调用get(),所以它应该总是对简单的日期格式化程序保持相同的引用。因此,正如Neil所说,当Thread终止时,简单日期格式化程序的唯一实例应该有资格进行垃圾收集。

如果你有数字或其他形式的调试显示这引入资源问题,那么这是另一个故事。但我相信这不应该是一个问题。

答案 5 :(得分:0)

在你的例子中,使用ThreadLocal应该没有问题。

当线程本地设置为由类加载器加载的实例以便稍后卸载时,线程本地(以及单例也!)成为问题。 servlet容器中的典型情况(如tomcat):

  1. webapp在请求处理期间设置本地线程。
  2. 该线程由容器管理。
  3. 应该取消部署webapp。
  4. 无法使用webapp的类加载器,因为还有一个引用:从实例的本地线程到其类到其类加载器。
  5. 与单身人士(由webapp提供的java.sql.DriverManger和JDBC驱动程序)相同。

    因此,特别是在完全无法控制的环境中,请避免使用此类内容!

答案 6 :(得分:0)

使用后清理本地线程,添加servlet过滤器:

protected ThreadLocal myThreadLocal = new ThreadLocal();

public void doFilter (ServletRequest req, ServletResponse res, chain) throws IOException, ServletException {
    try {
        // Set ThreadLocal (eg. to store heavy computation result done only once per request)
        myThreadLocal.set(context());

        chain.doFilter(req, res);
    } finally {
        // Important : cleanup ThreaLocal to prevent memory leak
        userIsSmartTL.remove();
    }
}

答案 7 :(得分:0)

上面的代码示例没有问题,因为它在静态变量中使用了 ThreadLocal。

当您将 ThreadLocals 的实例初始化为非静态变量时,会出现 ThreadLocal 内存泄漏的一个问题。当持有该变量的对象被垃圾回收时,ThreadLocal 的引用保留在线程中。如果随后在某种循环中实例化并使用许多 ThreadLocal,则会导致内存泄漏。

我在 netty 的 FastThreadLocal 上遇到了这个问题(我猜 java ThreadLocal 应该也有同样的问题)。我的解决方案是在 ThreadLocal 中使用弱引用映射值来解决此问题。这允许使用 ThreadLocal 变量作为实例变量,在释放持有对象时可以对其进行垃圾回收。

这里的代码(可以用来代替 ThreadLocals): https://github.com/invesdwin/invesdwin-util/blob/master/invesdwin-util-parent/invesdwin-util/src/main/java/de/invesdwin/util/concurrent/reference/WeakThreadLocalReference.java