San*_*dal 49 java multithreading memory-leaks permgen thread-local
在多个帖子中提到:不当使用ThreadLocal
原因内存泄漏.我正在努力了解内存泄漏将如何发生ThreadLocal
.
我发现它的唯一情况如下:
Web服务器维护一个线程池(例如,用于servlet).如果
ThreadLocal
没有删除变量,那些线程可以创建内存泄漏,因为线程没有死亡.
这种情况没有提到"Perm Space"内存泄漏.这是内存泄漏的唯一(主要)用例吗?
MRa*_*ser 71
与组合使用的PermGen耗尽ThreadLocal
通常是由类加载器泄漏引起的.
示例:
想象一下具有工作线程池的应用程序服务器.
它们将保持活动状态直到应用程序服务器终止
部署的Web应用程序在其中一个类中使用静态 ThreadLocal
,以便存储一些线程本地数据,SomeClass
即Web应用程序的另一个类(允许调用它)的实例.这是在工作线程内完成的(例如,此操作源自HTTP请求).
重要:
根据定义,在"拥有"线程死亡或者ThreadLocal本身不再可访问之前,将保留对ThreadLocal
值的引用.
如果Web应用程序无法清除参考到ThreadLocal
上关机,不好的事情会发生:
由于工作线程通常不会死和参考ThreadLocal
是静态的,该ThreadLocal
值仍然引用的情况下SomeClass
,Web应用程序的类- 即使Web应用程序已停止!
因此,Web应用程序的类加载器无法进行垃圾回收,这意味着Web应用程序的所有类(以及所有静态数据)都会保持加载状态(这会影响PermGen内存池以及堆).
Web应用程序的每次重新部署迭代都会增加permgen(和堆)的使用.
=>这是permgen泄漏这种泄漏的
一个流行的例子是 log4j中的这个错误(同时修复).
Nei*_*den 30
这个问题的公认答案以及Tomcat关于这个问题的"严重"日志都是误导性的.关键的话是:
根据定义,保持对ThreadLocal值的引用,直到"拥有"线程终止或者ThreadLocal本身不再可访问为止.[我的重点].
在这种情况下,对ThreadLocal的唯一引用位于类的静态final字段中,该类现在已成为GC的目标,以及来自工作线程的引用.但是,从工作线程到ThreadLocal的引用是WeakReferences!
但是,ThreadLocal的值不是弱引用.因此,如果您在ThreadLocal 的值中引用了应用程序类,那么这些将保留对ClassLoader的引用并阻止GC.但是,如果你的ThreadLocal值只是整数或字符串或其他一些基本对象类型(例如,上面的标准集合),那么应该没有问题(它们只会阻止引导/系统类加载器的GC,这是永远不会发生).
当你完成它时,明确地清理ThreadLocal仍然是一个好习惯,但是在引用的log4j bug的情况下,天空肯定没有下降(正如你从报告中看到的那样,值是一个空的Hashtable).
这是一些要演示的代码.首先,我们创建一个基本的自定义类加载器实现,没有父项在完成时打印到System.out:
import java.net.*;
public class CustomClassLoader extends URLClassLoader {
public CustomClassLoader(URL... urls) {
super(urls, null);
}
@Override
protected void finalize() {
System.out.println("*** CustomClassLoader finalized!");
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们定义一个驱动程序应用程序,它创建这个类加载器的新实例,使用它来加载一个带有ThreadLocal的类,然后删除对类加载器的引用,允许它进行GC.首先,在ThreadLocal值是对自定义类加载器加载的类的引用的情况下:
import java.net.*;
public class Main {
public static void main(String...args) throws Exception {
loadFoo();
while (true) {
System.gc();
Thread.sleep(1000);
}
}
private static void loadFoo() throws Exception {
CustomClassLoader cl = new CustomClassLoader(new URL("file:/tmp/"));
Class<?> clazz = cl.loadClass("Main$Foo");
clazz.newInstance();
cl = null;
}
public static class Foo {
private static final ThreadLocal<Foo> tl = new ThreadLocal<Foo>();
public Foo() {
tl.set(this);
System.out.println("ClassLoader: " + this.getClass().getClassLoader());
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我们运行它时,我们可以看到CustomClassLoader确实没有被垃圾收集(因为主线程中的本地线程具有对我们的自定义类加载器加载的Foo实例的引用):
$ java Main ClassLoader: CustomClassLoader@7a6d084b
但是,当我们将ThreadLocal更改为包含对简单Integer而不是Foo实例的引用时:
public static class Foo {
private static final ThreadLocal<Integer> tl = new ThreadLocal<Integer>();
public Foo() {
tl.set(42);
System.out.println("ClassLoader: " + this.getClass().getClassLoader());
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们看到自定义类加载器现在是垃圾收集的(因为主线程上的本地线程只引用了系统类加载器加载的整数):
$ java Main ClassLoader: CustomClassLoader@e76cbf7 *** CustomClassLoader finalized!
(Hashtable也是如此).所以在log4j的情况下,他们没有内存泄漏或任何类型的错误.他们已经清除了Hashtable,这足以确保类加载器的GC.IMO,这个bug出现在Tomcat中,它会在关闭所有未明确的.stove()d的ThreadLocals时不加选择地记录这些"SEVERE"错误,无论它们是否拥有对应用程序类的强引用.似乎至少有一些开发人员正在投入时间和精力来"修复"模糊内存泄漏的说法 - 如果马虎的Tomcat日志.
归档时间: |
|
查看次数: |
34456 次 |
最近记录: |