ThreadLocal是否真有内存泄漏
转自 http://my.oschina.net/xpbug/blog/113444
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | publicclassTest {    publicstaticvoidmain(String[] args) throwsInterruptedException {        ThreadLocal tl = newMyThreadLocal();        tl.set(newMy50MB());                tl=null;                System.out.println("Full GC");        System.gc();    }        publicstaticclassMyThreadLocal extendsThreadLocal {        privatebyte[] a = newbyte[1024*1024*1];                @Override        publicvoidfinalize() {            System.out.println("My threadlocal 1 MB finalized.");        }    }        publicstaticclassMy50MB {        privatebyte[] a = newbyte[1024*1024*50];                @Override        publicvoidfinalize() {            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回收.
ThreadLocal就隐含了生命周期绑定到PCB(线程控制块)的意思
从中可以看出,弱引用只存在于key上,所以key会被回收. 而value还存在着强引用.只有thead退出以后,value的强引用链条才会断掉. 看下面改进后的例子.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | publicclassTest2 {    /**     * @param args     * @throws InterruptedException      */    publicstaticvoidmain(String[] args) throwsInterruptedException {        newThread(newRunnable() {            @Override            publicvoidrun() {                ThreadLocal tl = newMyThreadLocal();                tl.set(newMy50MB());                                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呢?
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号