Java中的四种引用
Java中的四种引用
Java中有四种引用类型:强引用、软引用、弱引用、强引用
Java中为什么要有设计这四种引用
Java的内存分配和内存回收,都不需要程序员自己负责的,都是由虚拟机负责,而虚拟机判断一个对象是否要回收,就是看是否有引用指向此对象,也就是用可行性分析
设计四种引用的目的有两个:
- 可以让程序员通过代码的方式决定某个对象的生命周期
- 有利于使用垃圾回收
强引用
强引用的表现:
Object o = new Object();
像这种主动去new一个对象的引用,就是强引用,这种对象永远不会被回收,即使内存不知,JVM只会爆OOM,也不是去回收
如果想要它被回收可以主动把强引用和对象的关联中断,也就是断掉指针
o = null
可以通过手动调用GC,看看指针断裂之后,资源会不会被回收,为了方便观察回收的情况,我这里重新写一个类,重写finalize方法
public class TestQuote {
@Override
protected void finalize() throws Throwable{
System.out.println("TestQuote 被回收了");
}
public static void main(String[] args) {
TestQuote testQuote = new TestQuote();
testQuote = null;
System.gc();
}
}
运行结果:
软引用
先看如果创建一个软引用:
软引用就是把对象用SoftReference包裹一下,当我们需要从软引用对象获得包裹的对象,只需要get一下就好了
SoftReference<TestQuote> testQuoteSoftReference = new SoftReference<>(new TestQuote());
TestQuote testQuote = testQuoteSoftReference.get();
System.out.println(testQuote);
软引用的特点是:
当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用包裹的对象给清除,也就是当JVM的内存不足的时候,才会回收该对象。
下面模拟一下内存不足的清除状况
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024*1024*10];
System.out.println(softReference.get());
这里我定义了一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10M的byte[]
然后我们需要改一个参数:
-Xmz20M
代表最大的堆内存为20M
运行结果:
可以很清楚看到手动完成GC后,软引用对象包裹的byte[]还活得好好的,但是当再创建一个10M的byte[]后,最大堆内存不够用,所以把软引用对象包裹的byte[]给清除了,如果不清除,就会抛出OOM
软引用比较合适用作缓存,当内存足够的时候,可以正常的拿到缓存,当内存不够,就会先干掉缓存,不至于马上抛出OOM
弱引用
弱引用的使用和软引用类型,只是关键字变成了WeakReference:
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1024*1024*10]);
System.out.println(weakReference.get());
弱引用的特点是不管内存是否足够,只要发生GC,都会被回收:
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1024*1024*10]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
运行结果:
可以看到内存还是很足够的时候,但是触发了GC,资源还是内回收了
弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap
虚引用
虚引用又被称为幻影引用
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<>(new byte[1],queue);
System.out.println(reference.get());
虚引用的使用和上面的软引用和弱引用的区别还是挺大了,这里先不管ReferenceQueue是什么东西
直接运行
直接输出了null,这里先去看看get方法的源码:
直接返回了null,这就是虚引用的特点之一:无法通过虚引用来获取对一个对象的真实引用。
那虚引用的使用有什么意义,这里先看回创建虚引用的代码
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<>(new byte[1],queue);
System.out.println(reference.get());
创建虚引用对象,这里除了把包裹对象传进去,还传进了一个ReferenceQueue,从名字就可以看出它是一个队列。
虚引用的特点之二就是虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用就会再回收之前,把这个虚引用加入到与之关联的ReferenceQueue中
接下来实践一下:
ReferenceQueue queue = new ReferenceQueue();
List<byte[]> bytes = new ArrayList<>();
PhantomReference<TestQuote> reference = new PhantomReference<TestQuote>(new TestQuote(),queue);
new Thread(() -> {
for (int i = 0; i < 100; i++) {
bytes.add(new byte[1024*1024*1024*1024*1024*1024*1024]);
}
}).start();
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
运行结果
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。
从运行结果可以看到:当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
运行结果
[外链图片转存中…(img-kVGMRh0s-1596903475719)]
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。
从运行结果可以看到:当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
在NIO
中,就运用了虚引用管理堆外内存