WeakReference
(弱引用) ,用来描述非必须存在的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
在JDK1.2之后提供了WeckReference
类来实现弱引用。
看上面的描述,我们在业务开发中好像很少甚至基本没有机会使用这个类,打个比方我们在方法中新建一个对象 A a = new A()
我们不可能写成 WeckReference<A> a = new WeckReference<>(new A())
这样, 因为这是个弱引用,万一我方法没执行完成,GC生效了,把我这个对象回收了,接着执行获取到的是个null对象,就出乱子了。
下面就说一下 WeakReference
在 Java
中与 ThreadLocal
类的经典搭配使用。
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("1");
先看上面两行代码会发生什么事情 ,例如当前线程名称叫做A,
A线程对象内部有一个 名叫threadLocals
的 ThreadLocal.ThreadLocalMap
类型对象。
ThreadLocal.ThreadLocalMap
对象中有一个类型为 ThreadLocal.ThreadLocalMap.Entry
对象数组。
ThreadLocal.ThreadLocalMap.Entry
这个对象引用在当前线程对象中创建的 ThreadLocal
类型对象,
当我们执行 threadLocal.set("1");
这行代码的时候, 这个方法会判断当前线程的 ThreadLocalMap
中有没有 key
和当前 threadLocal
对象相等的 Entry
,
如果有则替换Entry
对象的value
,如果没有,就新建一个key
为 threadLocal
对象的 Entry
添加到 线程的ThreadLocalMap
中去,
这里的Entry
就是弱引用,为什么这么设置?我们上面说了 一个 Entry
就保存着一个 ThreadLocal
对象,假设Entry
是强引用对象,我们现在有这么一段需求,
例如 threadLocal = null;
, 我们把这个对象引用置为null
,希望下次GC
把threadLocal
引用的对象一起回收掉,此刻虽然threadLocal
引用已经被置为空了,但是它之前引用的对象能被回收掉吗?
不可以,因为虽然threadLocal
是 null
了,但是还有ThreadLocalMap
中的 Entry
在引用着这个实际对象,导致GC
无法正常回收到,这显然不是我们想要看到的,如果这个线程一直存在的话,不停的在线程的方法栈中创建使用ThreadLocal threadLocal
,
然后这个变量threadLocal
虽然会随着栈帧销毁也没有,但是它指向的对象却因为还有Entry
在引用着,一直不回收,所以一直存在堆中,造成了内存泄漏
。
看到这里相信大家应该可以理解为什么Entry
是WeakReference
类型啦,
在ThreadLocal
使用的场景大概就是我们希望在ThreadLocalMap
对象中使用Entry
引用ThreadLocal
对象,但是我们希望我们这个Entry
的引用可以随着该对象在其他地方的强引用(ThreadLocal threadLocal
)消失而一起消失,如果以后在业务开发中碰到类似的场景,就可以用到弱引用了。
说到这里我们如果 threadLocal = null;
这么写?会不会有问题呢?
答案是有的,为什么?虽然我们Entry
指向的对象被回收了,但是我们这个Entry
对象还是存在线程的 ThreadLocalMap
对象当中的,无疑这也是一种泄露,所以得使用 threadLocal.remove()
将 Entry
对象一起清空最为稳妥。
下面举一个例子,这段伪代码是写在spring mvc中的方法
```
@RequestMapping()
public void select(boolean flag) {
if(flag) {
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("1");
}
get();
}
public void get() {
xxx = threadLocal.get();
......
}
```
上面的代码会造成内存泄漏,为什么?不是说好了弱引用吗,方法栈销毁后threadLocal
对应的对象不应该也就跟着没有了?
因为Spring MVC
是使用线程池的方式创建线程的,很多线程会被复用,所以虽然这个方法栈被销毁了,但是这个线程可能由于线程池的配置是一直存在的,所以根据上面的说法我们没有执行threadLocal.remove()
会导致Entry
对象一直存在于线程对象中,
甚至可能会因为GC
没有及时清理,导致下次访问参数flag
是false
时,获取到了之前线程set
的值,所以建议大家在使用线程池的地方使用ThreadLocal
要及时remove()
,以前造成内存泄漏和其他方面安全性的问题,如果你用的线程时用完就销毁的,那可以不用考虑这个事情,但是现在的框架基本都是用线程池的方式获取线程,所以必须考虑这个问题,资源要及时释放。