一、java的引用类型
java的引用类型有四种:强、软、弱、虚
1、强引用
我们平常创建的对象基本上都是强引用,也就是new出来的对象,如Object o = new Object();
,这个o直接指向堆内存当中创建的Object对象,如下:
GC:强引用类型只有在没有引用指向这个对象的时候它才能被回收,否则,即使内存不足,也不会被回收
2、软引用
SoftReference<Object> sr = new SoftReference<>(new Object());
// 获取其中元素
sr.get();
如上这句代码就有一个软引用,实际上,我们直接创建的引用一般都是强引用,也就是,我们无法直接指向软引用,如下,我们创建的 sr 对new SoftReference()是强引用,但是new SoftReference()对new Object()是软引用,相当于起到一个中介的作用,也就是 sr 无法直接软引用指向new Object()对象
GC:软引用在内存不足的时候会被回收,内存足够的话不会被回收
应用:软引用适合在缓存中使用
3、弱引用
WeakReference<Object> wr = new WeakReference<>(new Object());
// 获取其中元素
wr.get();
如上就是一个弱引用的例子,wr 直接强引用指向new WeakReference<>(),new WeakReference<>()则弱引用指向new Object()
GC:弱引用在垃圾回收的时候直接进行回收
应用:ThreadLocal
4、虚引用
// 引用队列
private static final ReferenceQueue<Object> QUEUE = new ReferenceQueue<>();
PhantomReference<Object> pr = new PhantomReference<>(new Object(),QUEUE);
// 虚引用无法获取其中元素
pr.get(); // 结果为null
如下,内存结构和软、弱引用差不多,只是PhantomReference<>()是虚引用指向new Object(),虚引用甚至无法获取其中元素
GC:虚引用被回收的时候会被放到一个队列里面,等待jvm单独处理
使用:直接内存的管理;内存一般有操作系统内存以及虚拟机内存,平时正常的请求信息先到达操作系统内存,虚拟机再去从操作系统内存中拷贝一份到虚拟机内存中使用,效率相对较低,所以jvm就使用一个对象来直接指向操作系统内存
二、ThreadLocal
// ThreadLocal 定义
static ThreadLocal<Object> tl = new ThreadLocal<>();
// 添加元素
tl.set(new Object());
// 获取其中元素
tl.get();
ThreadLocal其实只是线程的一个成员变量,也就是,每当我们创建一个线程的时候,都会生成一个ThreadLocalMap的一个容器对象;当我们调用 set() 方法的时候,其实就是将我们自己创建的 tl 作为map的键,new Object() 作为值被存入到当前线程的ThreadLocalMap当中,即threadLocals.put(tl,new Object());
所以,当我们在线程1中调用set()方法的时候,它会把数据存到线程1的ThreadLocalMap中,当我在线程2调用get()方法时,则会从线程2的ThreadLocalMap中去取数据,使用的容器不一样
如下图,当我们调用ThreadLocal的 set() 方法的时候,会将 tl 作为键,Object作为值存入map中,map中的key对 tl 是弱引用,value对Object则是强引用
内存泄露
内存泄露问题:如果map中的key对ThreadLocal的引用类型是强引用的话,那如果我们用不到ThreadLocal了,想要回收ThreadLocal,此时即使我们将 tl 置为空(null),但是map中的key对ThreadLocal是强引用,就会导致ThreadLocal不会被回收;
但是如果map中的key对ThreadLocal是弱引用的话,我们只需要将 tl 置为空,GC的时候就会直接回收ThreadLocal;
但此时value依然是强引用,依然不会被回收,所以map每次在我们执行get(),set()方法的时候,都会自动remove键为null的value,但是我们依然需要在不使用ThreadLocal之后,自己手动将value值remove,因为get(),set()有可能长时间不执行
线程池问题:存在一种情况,线程池中线程1在被使用的时候,在ThreadLocalMap中存入了一些数据,之后又被放回线程池,然后又被调用执行,此时可能拿到之前存入的数据,所以,线程池每次线程执行完的时候,都会清理ThreadLocalMap当中的值