【发布时间】:2020-10-18 06:06:45
【问题描述】:
我有一个 .war 项目,其中包含在接收 HTTP 调用之前和之后设置和重置 ThreadLocal<String> 的逻辑。从我运行的测试中,我很确定这两个操作都正确执行,即 ThreadLocal 在设置后很快被清除。
为了测试我的项目,我有一个创建 10 个线程并从这些线程调用服务器的程序。等待 1 秒后,此过程重复约 500 次。我的目标是监控正在消耗的堆存储并确保没有内存泄漏。
以下是我所做的重要观察:
- 在未使用 ThreadLocal 时调用服务器:堆大小在整个测试期间不断增加,并在测试完成后降至测试前水平。
- 在使用 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<String>实例和十个线程,你想,他们负责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