Pak*_*ira 5 java multithreading garbage-collection memory-leaks
我想了解如何Threadlocal导致Classloader泄漏。所以为此我有这个代码
public class Main {
public static void main(String... args) throws Exception {
loadClass();
while (true) {
System.gc();
Thread.sleep(1000);
}
}
private static void loadClass() throws Exception {
URL url = Main.class.getProtectionDomain()
.getCodeSource()
.getLocation();
MyCustomClassLoader cl = new MyCustomClassLoader(url);
Class<?> clazz = cl.loadClass("com.test.Foo");
clazz.newInstance();
cl = null;
}
}
class MyCustomClassLoader extends URLClassLoader {
public MyCustomClassLoader(URL... urls) {
super(urls, null);
}
@Override
protected void finalize() {
System.out.println("*** CustomClassLoader finalized!");
}
}
Run Code Online (Sandbox Code Playgroud)
Foo.java
public class Foo {
private static final ThreadLocal<Bar> tl = new ThreadLocal<Bar>();
public Foo() {
Bar bar = new Bar();
tl.set(bar);
System.out.println("Test ClassLoader: " + this.getClass()
.getClassLoader());
}
@Override
protected void finalize() {
System.out.println(this + " finalized!");
}
}
Run Code Online (Sandbox Code Playgroud)
酒吧.java
public class Bar {
public Bar() {
System.out.println(this + " created");
System.out.println("Bar ClassLoader: " + this.getClass()
.getClassLoader());
}
@Override
public void finalize() {
System.out.println(this + " finalized");
}
}
Run Code Online (Sandbox Code Playgroud)
运行此代码后,它显示未调用 Finalize,仅MyCustomClassloader 调用了 Finalize。但是当我将 Threadlocal 更改为 String 时,所有的finalize都会被调用。BarFoo
public class Foo {
private static final ThreadLocal<String> tl = new ThreadLocal<String>();
public Foo() {
Bar bar = new Bar();
tl.set("some");
System.out.println("Test ClassLoader: " + this.getClass()
.getClassLoader());
}
Run Code Online (Sandbox Code Playgroud)
String您能解释一下为什么使用 ThreadLocal as和 ThreadLocal时会有差异吗Bar?
当您将线程局部变量设置为 的实例时Bar,该值具有对其定义类加载器的隐式引用,该类加载器也是 的定义类加载器,Foo因此具有对其保存.statictlThreadLocal
相反,该类String是由引导加载程序定义的,并且没有对该Foo类的隐式引用。
现在,引用循环本身并不会阻止垃圾收集。如果只有一个对象持有对循环成员的引用,并且该对象变得不可访问,则整个循环将变得不可访问。这里的问题是仍然引用循环的对象是Thread仍然活着的。
ThreadLocal特定值与实例和实例的组合相关联Thread,我们\xe2\x80\x99d希望如果其中任何一个变得无法访问,它将停止引用该值。不幸的是,不存在这样的功能。我们只能将一个值与一个对象的可达性相关联,就像与 a 的键相关联WeakHashMap,但不能与两个对象的键相关联。
在 OpenJDK 实现中,Thread是此构造的所有者,这使其免受反向引用Thread. 例如
ThreadLocal<Thread> local = new ThreadLocal<>();\n\nReferenceQueue<Thread> q = new ReferenceQueue<>();\n\nSet<Reference<?>> refs = ConcurrentHashMap.newKeySet();\n\nnew Thread(() -> {\n Thread t = Thread.currentThread();\n local.set(t);\n refs.add(new WeakReference<>(t, q));\n}).start();\n\nReference<?> r;\nwhile((r = q.remove(2000)) == null) {\n System.gc();\n}\n\nif(refs.remove(r)) System.out.println("Collected");\nelse System.out.println("Something very suspicuous is going on");\nRun Code Online (Sandbox Code Playgroud)\n这将打印Collected,表明从值到 的引用并Thread没有阻止删除,与put(t, t)不同WeakHashMap。
代价是这个构造不能避免对实例的反向引用ThreadLocal。
ReferenceQueue<Object> q = new ReferenceQueue<>();\n\nSet<Reference<?>> refs = ConcurrentHashMap.newKeySet();\n\ncreateThreadLocal(refs, q);\n\nReference<?> r;\nwhile((r = q.remove(2000)) == null) {\n System.gc();\n}\n\nif(refs.remove(r)) System.out.println("Collected");\nelse System.out.println("Something very suspicuous is going on");\nRun Code Online (Sandbox Code Playgroud)\nstatic void createThreadLocal(Set<Reference<?>> refs, ReferenceQueue<Object> q) {\n ThreadLocal<ThreadLocal<?>> local = new ThreadLocal<>();\n local.set(local);\n refs.add(new WeakReference<>(local, q));\n}\nRun Code Online (Sandbox Code Playgroud)\nThreadLocal这将永远挂起,因为只要关联的线程仍然处于活动状态,从 到 自身的反向引用就会阻止其垃圾回收。
您的情况只是它的一个特殊变体,因为反向引用是通过Bar实例(其定义加载程序)到Foo\xe2\x80\x99sstatic变量。但原理是一样的。
你只需要改变线路
\nloadClass();\nRun Code Online (Sandbox Code Playgroud)\n到
\nnew Thread(new FutureTask(() -> { loadClass(); return null; })).start();\nRun Code Online (Sandbox Code Playgroud)\n停止该值与主线程关联。然后,类加载器以及所有关联的类和实例都会被垃圾收集。
\n| 归档时间: |
|
| 查看次数: |
298 次 |
| 最近记录: |