反驳:Threadlocal存在内存泄露

最近看到网上的一篇文章,分析说明ThreadLocal是如何内存泄露的. 但我不这么认为. ThreadLocal设计的很好,根本不存在内存泄露问题. 本文就结合图和代码的例子来验证个人见解. ide

网上的代码例子广泛是这样子的: spa

public class Test { .net

public static void main(String[] args) throws InterruptedException { 线程

ThreadLocal tl = new MyThreadLocal(); 设计

tl.set(new My50MB()); code

tl=null; 对象

System.out.println("Full GC"); 接口

System.gc(); 生命周期

} 内存

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.

Thread.sleep 1秒是为了给GC一个反应的时间. GC优先级低,即便调用了system.gc也不能马上执行.因此sleep 1秒.

不少人就开始分析了: threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用之后,map里面的value却没有被回收.而这块value永远不会被访问到了. 因此存在着内存泄露. 最好的作法是将调用threadlocal的remove方法.

说的也比较正确,当value再也不使用的时候,调用remove的确是很好的作法.但内存泄露一说却不正确. 这是threadlocal的设计的不得已而为之的问题.

首先,让咱们看看在threadlocal的生命周期中,都存在哪些引用吧. 看下图: 实线表明强引用,虚线表明弱引用.

每一个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每一个key都弱引用指向threadlocal. 像上面code中的例子,当把threadlocal实例tl置为null之后,没有任何强引用指向threadlocal实例,因此threadlocal将会被gc回收. 可是,咱们的value却不能回收,由于存在一条从current thread链接过来的强引用. 只有当前thread结束之后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将所有被GC回收.

从中能够看出,弱引用只存在于key上,因此key会被回收. 而value还存在着强引用.只有thead退出之后,value的强引用链条才会断掉.看下面改进后的例子.

public class Test2 {

/**

* @param args

* @throws InterruptedException

*/

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();

Thread.sleep(1000);

System.gc();

Thread.sleep(1000);

System.gc();

Thread.sleep(1000);

}

这一次的打印将输出:

Full GC

My threadlocal 1 MB finalized.

My 50 MB finalized.

咱们能够看到,全部的都回收了.为何要屡次调用system.gc()? 这和finalize方法的策略有关系. finalize是一个特别低优先级的线程,当执行gc时,若是一个对象须要被回收,先执行它的finalize方法.这意味着,本次gc可能没法真正回收这个具备finalize方法的对象.留待下次回收. 这里屡次调用system.gc正是为了给finalize留些时间.

从上面的例子能够看出,当线程退出之后,咱们的value被回收了. 这是正确的.这说明内存并无泄露. 栈中还存在着对value的强引用路线.只是因为thread没有提供public接口,没法访问此value,但咱们可使用反射拿到这个value.

这也是不得已而为之的设计吧. 总之,若是不想依赖线程的生命周期,那就调用remove方法来释放value的内存吧. 让咱们好好思考一下,有什么办法能够在tl=null的时候,也释放value呢?

相关文章
相关标签/搜索