WeakReference (弱引用) ,用来描述非必须存在的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
在JDK1.2之后提供了WeakReference类来实现弱引用。
看上面的描述,我们在业务开发中好像很少甚至基本没有机会使用这个类,打个比方我们在方法中新建一个对象 A a = new A() 我们不可能写成 WeakReference<A> a = new WeakReference<>(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(),以前造成内存泄漏和其他方面安全性的问题,如果你用的线程时用完就销毁的,那可以不用考虑这个事情,但是现在的框架基本都是用线程池的方式获取线程,所以必须考虑这个问题,资源要及时释放。