在使用了线程池(如Executor)的情况下,那么即使父线程已经结束,子线程依然存在并被池化。这样,线程池中的线程在下一次请求被执行的时候,ThreadLocal对象的get()方法返回的将不是当前线程中设定的变量,因为池中的“子线程”根本不是当前线程创建的,当前线程设定的 ThreadLocal变量也就无法传递给线程池中的线程。因此,必须将外部线程中的ThreadLocal变量显式地传递给线程池中的线程。
于是有了如下分析:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块 value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法. 说的也比较正确,当value不再使用的时候,调用remove的确是很好的做法.但内存泄露一说却不正确。
在threadlocal的生命周期中,存在的引用如下图: 实线代表强引用,虚线代表弱引用:
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以 threadlocal将会被gc回收. 但是,value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
从上面的例子可以看出,当线程退出以后,value被回收了. 这是正确的.这说明内存并没有泄露.栈中还存在着对value的强引用路线。 然而在使用线程池时,线程一般是不会被销毁的。
参考:http://my.oschina.net/xpbug/blog/113444
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
ThreadLocal tl = new MyThreadLocal();
tl.set(new My50MB());
tl = null;
System.out.println("Full GC");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
public static class MyThreadLocal extends ThreadLocal {
private byte[] a = new byte[1024 * 1024 * 1];
@Override
public void finalize() {
System.out.println("My threadlocal 1 MB finalized.");
}
}
public static class My50MB {
private byte[] a = new byte[1024 * 1024 * 50];
@Override
public void finalize() {
System.out.println("My 50 MB finalized.");
}
}
}
运行结果:Full GC
My threadlocal 1 MB finalized.
TimeUnit.SECONDS.sleep(1)是为了给GC一个反应的时间. GC优先级低,即使调用了system.gc也不能立刻执行.所以sleep 1秒.于是有了如下分析:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块 value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法. 说的也比较正确,当value不再使用的时候,调用remove的确是很好的做法.但内存泄露一说却不正确。
在threadlocal的生命周期中,存在的引用如下图: 实线代表强引用,虚线代表弱引用:

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以 threadlocal将会被gc回收. 但是,value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
弱引用只存在于key上,所以key会被回收. 而value还存在着强引用.只有thead退出以后,value的强引用链条才会断掉:
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal tl = new MyThreadLocal();
tl.set(new My50MB());
tl = null;
System.out.println("Full GC");
System.gc();
}
}).start();
System.gc();
TimeUnit.SECONDS.sleep(1);
System.gc();
TimeUnit.SECONDS.sleep(1);
System.gc();
TimeUnit.SECONDS.sleep(1);
}
public static class MyThreadLocal extends ThreadLocal {
private byte[] a = new byte[1024 * 1024 * 1];
@Override
public void finalize() {
System.out.println("My threadlocal 1 MB finalized.");
}
}
public static class My50MB {
private byte[] a = new byte[1024 * 1024 * 50];
@Override
public void finalize() {
System.out.println("My 50 MB finalized.");
}
}
}
运行结果:Full GC
My threadlocal 1 MB finalized.
My 50 MB finalized.
可以看到,所有的都回收了.为什么要多次调用system.gc()? 这和finalize方法的策略有关系. finalize是一个特别低优先级的线程,当执行gc时,如果一个对象需要被回收,先执行它的finalize方法.这意味着,本次gc可能无法真正回 收这个具有finalize方法的对象.留待下次回收. 这里多次调用system.gc正是为了给finalize留些时间.
从上面的例子可以看出,当线程退出以后,value被回收了. 这是正确的.这说明内存并没有泄露.栈中还存在着对value的强引用路线。 然而在使用线程池时,线程一般是不会被销毁的。
参考:http://my.oschina.net/xpbug/blog/113444