文档章节

反驳:Threadlocal存在内存泄露

xpbug
 xpbug
发布于 2013/03/13 15:02
字数 943
阅读 7808
收藏 73

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

网上的代码例子普遍是这样子的:

public class Test {
	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();
	}
	
	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呢?




© 著作权归作者所有

xpbug
粉丝 304
博文 102
码字总数 125336
作品 0
浦东
程序员
私信 提问
加载中

评论(15)

首席撸出血
首席撸出血
。。。。。。。。。。。。。。。。。。。。。。。。。。
爱吃大肉包
爱吃大肉包
改进后的例子和原来的有偏差, 原来是线程的生命周期没结束。改进后的是生命周期结束了。 ThreadLocal 的引用Value的回收是伴随的Thread的生命周期
Flyer_cao
Flyer_cao

引用来自“Ambitor”的评论

本文这段话有问题-----“每个key都弱引用指向threadlocal. 像上面code中的例子,当把threadlocal实例tl置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用”

ThreadLocal作为key断掉强引用t1之后 会被GC的真正原因是因为ThreadLocalMap中的Entry 继承了 WeakReference<ThreadLocal> 注意 泛型是ThreadLocal对象,并没有包含Entry中的Object value,且后续并没有再用到这个ThreadLocal,所以ThreadLocal被GC掉了!还有一个 value并没有被current Thread 保留强引用,Thread里面很明显的强引用是对于 ThreadLocalMap对象!!!~ 真正保证了value是Map中的Entry对象有个Object value 强引用!!~~~
真正保证了value是Map中的Entry对象有个Object value 强引用!!👍👍👍👍
刘尼玛
刘尼玛
java的gc线程属于守护线程,守护线程是在程序中不存在任何用户线程的时候就会自己退出,当main里面的程序立刻跑完,守护线程是不会来得及跑的,所以有可能导致内存泄漏;如果通过sleep来延迟程序执行的时间,那手机线程就有可能会跑的到,这时候调gc就可以回收内存,所以还是有可能存在内存泄漏的,个人观点
Ambitor
Ambitor
本文这段话有问题-----“每个key都弱引用指向threadlocal. 像上面code中的例子,当把threadlocal实例tl置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用”

ThreadLocal作为key断掉强引用t1之后 会被GC的真正原因是因为ThreadLocalMap中的Entry 继承了 WeakReference<ThreadLocal> 注意 泛型是ThreadLocal对象,并没有包含Entry中的Object value,且后续并没有再用到这个ThreadLocal,所以ThreadLocal被GC掉了!还有一个 value并没有被current Thread 保留强引用,Thread里面很明显的强引用是对于 ThreadLocalMap对象!!!~ 真正保证了value是Map中的Entry对象有个Object value 强引用!!~~~
whaon
whaon
如果用线程池的话还是会泄露的
xpbug
xpbug 博主

引用来自“liuinsect”的评论

写得不错,不过内部的很多原因没有说得清楚:http://liuinsect.iteye.com/blog/1827012 大家可以看看 多多交流。

还不清楚么?引用图已经充分说明内部的原理了。只是不想从代码结构和GC原理来啰嗦了。毕竟那些都是基础。
liuinsect
liuinsect
写得不错,不过内部的很多原因没有说得清楚:http://liuinsect.iteye.com/blog/1827012 大家可以看看 多多交流。
supperman
supperman
ThreadLocal的ThreadLocalMap是作为Thread的成员变量存在的,每次ThreadLocalMap的set方法都会主动去清理key为null的entry,所以会自动回收的
ivin
ivin
mark一下,后面有空看啊!
反驳:Threadlocal存在内存泄露

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

GM_LV
2013/03/27
270
0
在 JNI 编程中避免内存泄漏

此文转自 IBM developerWorks JNI 编程简介 JNI,Java Native Interface,是 native code 的编程接口。JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native code;在...

IBMdW
2011/04/26
988
1
ThreadLocal可能引起的内存泄露

  threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用thr...

天天顺利
2018/06/15
68
0
Java并发编程之ThreadLocal内存泄漏探究

使用 ThreadLocal 不当可能会导致内存泄露,是什么原因导致的内存泄漏呢? 我们首先看一个例子,代码如下: / Created by cong on 2018/7/14. */public class ThreadLocalOutOfMemoryTest { ...

狂小白
2018/07/14
0
0
多线程并发神器--ThreadLocal

什么是ThreadLocal 可以理解成线程本地变量,传统的线程对一个变量操作时操作的是同一个对象,也存在线程安全的问题。 ThreadLocal是一个变量的本地副本,线程对变量的操作不会影响其他线程。...

java技术栈
2017/08/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

大数据最核心的关键技术——32个算法,记得收藏!

奥地利符号计算研究所的Christoph Koutschan博士在自己的页面上发布了一篇文章,提到他做了一个调查,参与者大多数是计算机科学家,他请这些科学家投票选出最重要的算法,以下是这次调查的结...

大数据金罗
6分钟前
2
0
spring boot The server time zone value 'PDT' is unrecognized or represents more than one time zone.

spring boot启动项目时,发现数据库抛如下错误 Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'PDT' is unrecognized or represen......

huangkejie
27分钟前
1
0
正则表达式匹配

请实现一个函数用来匹配包括 '.' 和 '*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配...

Garphy
今天
7
0
Laravel 5.1的多路由文件的配置

默认的路由配置文件只有一个, \app\Http\routes.php。 在同一个文件中写路由容易起冲突,文件会越来越大,就需要定义多个路由文件。 找到加载\app\Http\routes.php的文件, 打开\app\Provid...

mdoo
今天
5
0
Hibernate 5 开始使用指南前言

同时在面向对象软件和关系型数据库进行工作,可能会非常复杂和费时。数据在对象和数据库之间可能会不一致,然后导致开发成本会非常高。 Hibernate 是一个针对 Java 环境的对象关系映射(Obj...

honeymoose
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部