【问题标题】:ThreadLocal causing memory leak even after cleanup?ThreadLocal 即使在清理后也会导致内存泄漏?
【发布时间】:2020-10-18 06:06:45
【问题描述】:

我有一个 .war 项目,其中包含在接收 HTTP 调用之前和之后设置和重置 ThreadLocal<String> 的逻辑。从我运行的测试中,我很确定这两个操作都正确执行,即 ThreadLocal 在设置后很快被清除。 为了测试我的项目,我有一个创建 10 个线程并从这些线程调用服务器的程序。等待 1 秒后,此过程重复约 500 次。我的目标是监控正在消耗的堆存储并确保没有内存泄漏。

以下是我所做的重要观察:

  1. 使用 ThreadLocal 时调用服务器:堆大小在整个测试期间不断增加,并在测试完成后降至测试前水平。
  2. 在使用 ThreadLocal 时调用服务器(设置然后重置):对服务器的调用完成后,堆保留大部分已用空间。因此,内存正在泄漏。

根据我对 ThreadLocals 使用的了解,如果不正确重置它们可能会导致内存泄漏。但是,就我而言,我正在采取适当的措施确保清除 ThreadLocal(在 finally 块中重置 ThreadLocal。话虽如此,可能导致此内存泄漏的原因是什么,我该如何解决它?

P.S.:以下是项目中的一些相关代码 sn-ps:

// Class for handling the ThreadLocal instances
public class EsStateStorage {

    private static ThreadLocal<String> outboundRequestThread = new ThreadLocal<>();
    private static ThreadLocal<String> inboundRequestThread = new ThreadLocal<>();

    public static String getOutboundRequestThread() {
        return outboundRequestThread.get();
    }

    public static void setOutboundRequestThread(String value) {
        outboundRequestThread.set(value);
    }

    public static void clearOutboundThreadStorage() {
        outboundRequestThread.remove();
    }

    public static String getInboundRequestThread() {
        return inboundRequestThread.get();
    }

    public static void setInboundRequestThread(String value) {
        inboundRequestThread.set(value);
    }

    public static void clearInboundThreadStorage() {
        inboundRequestThread.remove();
    }

}
// Setting the ThreadLocal Value before making the HTTP call via rest-template
public TestData getData(...) throws{
    ...
    EsStateStorage.setOutboundRequestThread("TEST_VALUE");
    return restTemplate.exchange(...).getBody();
}
// Resetting the thread storage in interceptors that capture all incoming/outgoing HTTP calls
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    try {
        ClientHttpResponse response = execution.execute(request, body);
        logOutboundCall(request, body, response);
        return response;
    } finally {
        EsStateStorage.clearOutboundThreadStorage();
    }
}

【问题讨论】:

  • 那么你有两个ThreadLocal&lt;String&gt; 实例和十个线程,你想,他们负责1GB 内存消耗?
  • @Holger 可能不是,但我担心的是当我使用ThreadLocal 时没有进行垃圾清理,从图中可以看出。在我在问题中提到的两种情况之间,我所做的唯一更改是取消使用 ThreadLocal。其他一切都保持不变。这使我得出结论,ThreadLocal 以某种方式导致了已用堆空间的保留。因此我的问题
  • 如果在堆大小达到 1500MB 时继续测试会发生什么?除此之外,还不清楚当你“不使用 ThreadLocal”时代码实际上有何不同。通常,当您使用它们时,它们会实现一个目的,并且当该功能不可用时,受影响的代码必须彻底改变。
  • 从第一张图中的趋势来看,我假设只要我不断地对服务器进行 HTTP 调用,堆大小就会不断增加(超过 1500MB)。此外,当我不使用 ThreadLocal 时,代码更改很少。在上面附加的代码 sn-ps 中,为了禁用 ThreadLocal 使用,我只是让 EsStateStorage 类的方法在被调用时不执行任何操作。
  • 好吧,getOutboundRequestThread()getInboundRequestThread() 方法 return 一些东西,如果这个类不再存储值,它们必须返回 null 或虚构的值,所以换句话说,调用代码可能会基于此表现出完全不同的行为。关于行为,不要猜测,只是尝试。如果它不断增加,请使用强制堆大小限制运行并配置 JVM 以在内存不足错误时进行堆转储,然后分析堆转储以找出泄漏的位置。

标签: java tomcat memory-leaks jvm thread-local


【解决方案1】:

您可能使用了ThreadLocal&lt;SomeCustomClass&gt;,它阻止了它的类加载器,从而阻止了整个应用程序被垃圾收集。如果是这种情况,我建议您使用自定义的ThreadLocal 类,例如我创建的https://github.com/codesinthedark/ImprovedThreadLocal

【讨论】:

    猜你喜欢
    • 2015-04-14
    • 1970-01-01
    • 2015-11-28
    • 2021-05-02
    • 2011-11-25
    • 2013-07-31
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多