经过彻夜研究终于搞懂ThreadLocal内存泄漏的原因了

1.1 何为内存泄漏?

首先我们有必要了解,到底何为「内存泄漏」?笔者这里引用百度百科的解释。

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法

释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

1.2 ThreadLocal 内存泄漏问题 

在一个线程中方法main方法首先进栈,创建ThreadLocal堆内存开辟一块空间,然后引用指向threadLocal,最终经过一系列调用,执行完内存如下图显示

 1.2.1、为什么会导致内存泄漏?

如上图所示,在线程栈 Stack 中,有两个变量,objectThreadLocal 和 threadLocals,分别指向了声明的局部变量 ThreadLocal ,以及当前执行的线程中ThreadLocalMap, ThreadLocalMap中的 key 是弱引用,当线程执行完该方法之后就会弹栈,Stack 线程栈中的 objectThreadLocal 变量就会消失,因此 ThreadLocal 变量的强引用消失了,那么 ThreadLocal 变量只有 Entry 中的 key 对他引用,并且还是弱引用,因此这个 ThreadLocal 变量会被回收掉,导致 Entry 中的 key 为 null,而 value 变量还未被回收(可能指向了其他对象),因此 value 还一直存在 ThreadLocalMap 变量中,由于 ThreadLocal 被回收了,无法通过 key 去访问到这个 value,导致这个 value 一直无法被回收,ThreadLocalMap 变量的生命周期是和当前线程的生命周期一样长的,只有在当前线程运行结束之后才会清除掉 value,因此会导致这个 value 一直停留在内存中,导致内存泄漏

当然 JDK 的开发者想到了这个问题,在使用 set get remove 的时候,会对 key 为 null 的 value 进行清理,使得程序的稳定性提升

为了避免这种情况发生,就要求我们开发者必须要保持良好的编码习惯。

1.2.2、ThreadLocal内存泄漏的根源?

由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏

因为tomcat服务器的接受请求的线程是线程池,生命周期较长

1.2.3、正确使用ThreadLocal

public class Test {
	public static void main(String[] args) {
		ThreadLocal local = new ThreadLocal();
		local.set(new Test());
		local = null;
		// 手动触发GC,此时ThreadLocal被回收,那么value是否被回收呢?
		System.gc();
		// GC是异步执行的,主线程Sleep一会,等待对象回收
		ThreadUtil.sleep(1000);
	}

	// 对象被回收时触发
	@Override
	protected void finalize() throws Throwable {
		System.err.println("对象被回收...");
	}
}

结果:控制台无输出,value没有被回收,发生泄漏。

public class Test {
	public static void main(String[] args) {
		ThreadLocal local = new ThreadLocal();
		local.set(new Test());
		local.remove();//手动删除
		local = null;
		// 手动触发GC,此时ThreadLocal被回收,那么value是否被回收呢?
		System.gc();
		// GC是异步执行的,主线程Sleep一会,等待对象回收
		ThreadUtil.sleep(1000);
	}

	// 对象被回收时触发
	@Override
	protected void finalize() throws Throwable {
		System.err.println("对象被回收...");
	}
}

结果:控制台输出【对象被回收…】,没有泄漏。

remove源代码

最终我们看到在expungeStaleEntry中将value和Entry对象置为了null

总结

ThreadLocal 定义为局部变量,会导致方法执行完之后 ThreadLocal 被回收,而 value 没有被回收,导致无法通过 key 访问到这个 value。

导致内存泄漏,将 ThreadLocal 定义为 private static final,那么这个 ThreadLocal 不会被回收,可以随时通过这个 ThreadLocal 去访问到 value,随时可以手动回收,因此不会内存泄漏,但是会导致脏数据。

所以在 ThreadLocal 的内存泄漏问题主要是针对将 ThreadLocal 定义为局部变量的时候,如果不手动 remove 可能会导致 ThreadLocalMap 中的 Entry 对象无法回收,一直占用内存导致内存泄漏,直到当前 Thread 结束之后才会被回收

这里再说一下 ThreadLocal 的使用规范就是:将 ThreadLocal 变量定义为 private static final,并且在使用完,记得通过 try finall 来 remove 掉,避免出现脏数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值