这一天,程序员们终于想起「面试」支配的恐惧。
以前一直知道ThreadLocal如果使用不当会导致内存泄漏,并且还认为是ThreadLocal中弱应用导致的(这是错误的❌)。今天仔细看了下源码和文档发现并不是那么回事~所以今天来分析一下ThreaadLocal的底层原理和内存泄漏吧~在讲之前先科普一下Java中的4种引用。
4种引用基础知识
1.强引用
新建一个对象就是一个强引用。只有当没有对象引用才会被回收。
2.软引用
softReference软引用,假如某个对象只有一个软引用关联,那么只有当JVM内存不足的时候才会被回收。
3.弱引用
weakReference弱引用,假如某个对象只有一个弱引用关联,GC回收时,遇见直接回收。
4.虚引用
虚引用一般开发不需要用到,GC回收时不会真正回收该对象,而是放进queue中检查,当真正没有引用时才回收。
ThreadLocal底层原理
这次的源码分析基于一个非常简单的多线程代码,这里主要实现了一个threadlocal和两个Thread,并且画出了线程与堆栈的内存关系。
值得注意到的是,这里面共有4个引用,我会顺着引用类型讲解,并引申出内存泄漏问题。请耐心阅读。
1.第一根引用是通过new ThreadLocal直接新建的,所以这里是强引用。
2.第二根引用也是强引用。
a) 这里大家可能会有疑问,为什么这里又多出来了一个ThreadLocal?
众所周知ThreadLocal是每个kv对,具有线程隔离性的数据管理工具。具体是怎么实现 的呢?原来每个线程都有一个属于自己的threadLocals对象,并且默认为空。每次set时都会把当前(main)的ThreadLocal对象作为key,ThreadLocal里的泛型对象object作为value。
e.g. Thread中的ThreadLocal中key为tl.hash() / value 为 I am thread1 method。
3.第三根是kv对,强引用。
4.第4根是ThreadLocalMap特有的弱引用。
//每个线程都有自己的threadlocal对象
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
...
」
//这个方法是取自ThreadLocal的set方法,
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value); return value;
}
面试考点1:为什么用这里用弱引用呢?
如果用的是强引用,那么map中kv对无法正常释放会造成内存泄漏。
反之,如果是弱引用,1关联断开后,4由于是弱引用,并且没有其他地方使用,下次gc会自动清理
e.g.如上面的例子,1和4都强引用,无法正常释放。
面试考点2:ThreadLocal会造成内存泄漏,请问是什么原因,应该怎么避免?
会有这么一个场景,tl不再需要了,被手动清空了。这个时候map中key变成空,map中的value无法访问。造成内存泄漏。
解决方法:每次tl使用完以后手动remove掉,或者thread正常关闭,内存就会释放。