四大引用
强引用
特点:只有对象没有被引用的时候,才会被回收。
示例:Object o =new Object();
软引用
特点:内存不够的时候,软引用就会被GC掉
实例:SoftReference<Object> o1 = new SoftReference<>(new Object());
使用场景:缓存
弱引用
特点:系统只要GC,就会被回收掉
实例:WeakReference<Object> o2 = new WeakReference<>(new Object());
使用场景:ThreadLocal 中就有用到
虚引用
特点:用来管理直接内存的,它无法被捕获到,它被回收的时候,会给出一个信号,然后回收它的直接内存。
实例:
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
// 创建虚引用需要创建一个队列,用于接收被回收的对象
PhantomReference<Object> o3
= new PhantomReference<Object>(new Object(),referenceQueue);
// 返回的是null
System.out.println(o3.get());
使用场景:对直接内存的使用和管理
ThreadLocal
此图做参考:
其实 ThreadLocal 其实只是一个引子,能保证每个线程的数据访问隔离的其实是:每个线程维护了一个ThreaLocals(map)来存储每个线程对象的变量值。每个线程对象私有属性值,可不是互相隔离嘛。
而这个map,不是用的现成的Map,而是thread的一个静态内部类,它存储的形式和咱们常用的hashmap类似,都是一个 Entry 的形式,而 Entry 的 key 和 value 分别是:
key:ThreadLocal对象的弱引用(默认1.5以后,之前是ThreadId作为key)
value:线程的变量
核心源码:
// Entry的组成和弱引用的使用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
// 继承父类的构造方法
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// set方法
public void set(T value) {
Thread t = Thread.currentThread();// 获取到当前线程
ThreadLocalMap map = getMap(t);// 获取到现成对应的map
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
其他代码,大家可以自己去看一下,很简单!
为什么用ThreadLocal的弱引用做key,而不是直接使用前引用的呢?
注:T1是一个ThreadLocal对象
根据之前给大家的图,如果是强引用的话,如果这个 T1
被一个线程set值之后,只要这个线程不结束,那么 ThreadLocalMap
对 T1
的引用就一直存在, T1
就永远不会被GC。如果这个线程是一个长久执行的线程,但是这个 T1 只是线程初始化的时候引用一次,此时就会造成内存泄露。
因为弱引用的特点就是:只要GC,就会被回收。
如果此时 ThreadLocalMap
对 T1
是弱引用,当线程对 T1
使用结束之后,只要 把 T1
设置为 null ,下次 GC
的时候,T1
对应的那块内存将直接被回收掉。
这样就不会内存泄露的吗?
答案是否定的。
如果 T1 会回收之后,ThreadLocalMap
中 T1 对应的 key 就变成了 null
,此时 T1
对应 value
长时间没有被回收,也会造成内存溢出,所以我们在不用 ThreadLocal
中值的时候,需要先手动 remove
一下。
原因:虽然我们每次 对 ThreadLocalMap
进行 get
和 set
的时候,方法都会进行 null
值的判断,如果有 key
为 null
的,就会删除,但是如果这个线程很长时间不执行 get
set
的话,同样会造成内存泄露的问题。
简单总结,如有纰漏欢迎指出交流,感觉不错就点个赞呗