【问题标题】:ThreadLocal Resource Leak and WeakReferenceThreadLocal 资源泄漏和弱引用
【发布时间】:2010-10-30 17:49:48
【问题描述】:

我对@9​​87654321@ 的有限理解是它有resource leak issues。我认为可以通过将WeakReferences 与 ThreadLocal 正确使用来解决此问题(尽管我可能误解了这一点。)我只想要一个模式或示例,以正确使用带有 WeakReference 的 ThreadLocal(如果存在)。比如这段代码 sn-p 会在哪里引入 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);
    }
}

【问题讨论】:

  • 您为什么认为它存在资源问题?我问的原因是因为根据我的经验,我没有遇到过问题。
  • 请提供一些背景说明您认为存在内存泄漏的原因。
  • 已更新。请参阅上面的“资源泄漏问题”链接。
  • 我更新了我的答案以解决您的编辑问题。

标签: java thread-local weak-references


【解决方案1】:

不应该有这样的问题。

一个线程的 ThreadLocal 引用被定义为只存在于对应的线程活着(参见 javadoc)——或者换句话说,一旦线程不活着,如果 ThreadLocal 是对该对象的唯一引用,那么该对象就可以进行垃圾回收了。

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

【讨论】:

  • 考虑在应用服务器/容器中运行的情况,该应用服务器/容器可以卸载您的应用程序(例如,安装不同的版本)但继续使用线程来发布新的应用程序。
【解决方案2】:

只是补充一下@Neil Coffey 所说的,只要您的 ThreadLocal 实例是静态的,这应该不是问题。因为您一直在静态实例上调用 get(),所以它应该始终保持对您的简单日期格式化程序的相同引用。因此,正如尼尔所说,当线程终止时,简单日期格式化程序的唯一实例应该有资格进行垃圾回收。

如果您有数字或其他形式的调试显示此引入的资源问题,那就另当别论了。但我相信它不应该是一个问题。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • +1:我什至没有想到这一点。可以肯定的是,如果不是这个 OP,那么可能是其他人。
    【解决方案4】:

    在您的示例中,使用 ThreadLocal 应该没有问题。

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

    1. Web 应用在请求处理期间设置了本地线程。
    2. 线程由容器管理。
    3. 应该取消部署 web 应用程序。
    4. webapp 的类加载器不能被垃圾,因为剩下一个引用:从实例的本地线程到它的类到它的类加载器。

    单例也是如此(webapp 提供的 java.sql.DriverManger 和 JDBC 驱动程序)。

    因此,尤其是在不受您完全控制的环境中,请避免此类事情!

    【讨论】:

    • 不,格式化程序不是线程安全的。多个线程调用单个实例的 parse 方法是一场灾难。
    • 好吧,没考虑过SimpleDateFormat,不过ThreadLocal应该没问题。
    • 另见Tomcat's Memory Leak Protection。他们还描述了这个 ThreadLocal 泄漏,但不幸的是没有提出解决方法。当 webapp 被取消部署时,Tomcat 本身似乎试图取消 webapp 的类加载器类中的所有静态类引用。
    【解决方案5】:

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

    另外,ThreadLocal 的值实际上存储在Thread 中;如果一个线程死亡,所有通过ThreadLocal 与该线程关联的值都会被收集。

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


    更新:仅当存储在ThreadLocal 中的值强烈引用ThreadLocal(类似于循环引用)时,引用的问题才会出现。

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

    【讨论】:

    • 当线程局部值(间接)引用其 ThreadLocal 时泄漏仍然是 Sun 实现中的一个错误。我不确定疯狂的 Bob Lee 在 Harmony 中的实现是否有同样的问题。
    • 是 ThreadLocal 中的 bug,还是应用程序及其对 ThreadLocal 的使用?我还没有遇到它(我尽量避免使用 ThreadLocal),所以我仍然不确定。
    • 请注意,在 Servlet 环境中,您需要使用 servlet 过滤器来清除 ThreadLocal,如果您不这样做,您将在重新部署时出现大量泄漏
    【解决方案6】:

    我意识到这并不是对您问题的严格回答,但作为一般规则,我不建议在请求/交互结束时没有明确拆除的情况下使用 ThreadLocal。经典的做法是在 servlet 容器中执行此类操作,乍一看似乎很好,但由于线程是池化的,因此即使在处理完每个请求后,ThreadLocal 仍会挂在资源上成为问题。

    建议:

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

    【讨论】:

      【解决方案7】:

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

      protected ThreadLocal myThreadLocal = new ThreadLocal(); public void doFilter (ServletRequest req, ServletResponse res, chain) 抛出 IOException, ServletException { 尝试 { // 设置 ThreadLocal(例如,每个请求只存储一次繁重的计算结果) myThreadLocal.set(context()); 链.doFilter(req, res); } 最后 { // 重要:清理 ThraLocal 以防止内存泄漏 userIsSmartTL.remove(); } }

      【讨论】:

        【解决方案8】:

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

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

        我在 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-04-09
          • 2011-11-25
          • 2013-07-31
          • 2014-02-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-09
          相关资源
          最近更新 更多