Java中的引用按强度可依次分为:强引用,软引用,弱引用和虚引用。
如上图,除了强引用FinalReference外,其他三种引用类型均为public,可以在应用程序中直接使用。
强引用
强引用在代码之中是普遍存在的,我们写的代码绝大部分都是强引用。比如:
Object o = new Object();
String s = new String("hello");
强引用的特点:
只要某个对象与强引用有关联,那么这个对象就永远不会被回收。即使内存不足,JVM宁愿抛出OOM(内存溢出)异常,也不会去回收它。
但是强引用的这种特点会导致出现内存泄漏的问题,所以我们可以手动切断这些对象与强引用之间的关联,让JVM可以去回收它。如:
o = null;
s = null;
软引用
软引用就是使用关键字SoftReference将对象包裹起来:
SoftReference<String> stringSoftReference = new SoftReference<>(new String("hello"));
获取被包裹的对象也非常简单,只需要调用SoftReference对象的get方法就可以了:
String s = stringSoftReference.get();
System.out.println(s);
//打印结果:hello
软引用的特点:
当内存不足时会触发GC,然后如果GC之后内存还是不足,才会把软引用包裹的对象给干掉。也就是说,只有在内存不足的时候,JVM才会回收该对象。
比如我们可以做个实验,先将ide中堆的大小设为设为2M,然后运行下面的程序:
import java.lang.ref.SoftReference;
public class Test {
public static void main(String[] args) {
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024*1024]);
if(softReference.get() != null) {
System.out.println("对象没有被回收");
}else {
System.out.println("对象被回收了");
}
//看看gc之后对象有没有被回收
System.gc();
if(softReference.get() != null) {
System.out.println("对象没有被回收");
}else {
System.out.println("对象被回收了");
}
//再创建一个大小为1M的数组让堆的内存不足,看看对象会不会被回收
byte[] bytes = new byte[1024*1024];
if(softReference.get() != null) {
System.out.println("对象没有被回收");
}else {
System.out.println("对象被回收了");
}
}
}
//运行结果:
对象没有被回收
对象没有被回收
对象被回收了
软引用的作用就是: 它比较适合用作缓存,当内存足够的时候,可以正常的拿到缓存。当内存不足的时候就会先干掉这些缓存,然后不至于马上抛出OOM。
弱引用
弱引用就是使用WeakReference将对象包裹起来:
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[10]);
获取被包裹的对象同样只需要调用WeakReference对象的get方法就可以了:
byte[] bytes = weakReference.get();
弱引用的特点:
不管内存是否足够,只要发生GC,都会被回收。
import java.lang.ref.WeakReference;
public class Test {
public static void main(String[] args) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[10]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
}
}
//运行结果:
[B@1540e19d
null
可以看到明明内存很充足,但是在gc之后,对象还是被回收了。
弱引用的用途: 最常见的用处就是在哈希表中。因为哈希表中任何Java对象都可以作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存。所以对于这种情况的解决办法就是使用弱引用来引用这些对象,这样哈希表中的键和值对象都能被垃圾回收。Java中有一个WeakHashMap就是这个作用。
虚引用
也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。
它跟软引用和弱引用有点不同,除了要使用关键字PhantomReference将对象包裹起来外,还需要搭配ReferenceQueue来使用:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
//将ReferenceQueue对象作为第二个参数传进去
PhantomReference<byte[]> phantomReference = new PhantomReference<>(new byte[10],queue);
获取被包裹的对象同样也是调用get方法:
System.out.println(phantomReference.get());
//运行结果:
null
可以看到明明内存充足,而且也没有发生gc,为什么打印结果为null呢?点开其get方法源码可以看到:
public T get() {
return null;
}
可以看到只要调用了get方法,返回值均为null。为什么呢?
虚引用特点:
无法通过虚引用来获取对一个对象的真实引用。
前面提到了它必须配合ReferenceQueue一起使用。因为在GC准备回收一个对象时,如果发现它还有虚引用,就会在GC时,把这个虚引用加入到与之关联的ReferenceQueue队列中。好处就是如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
虚引用用途: 可以用来跟踪对象被GC的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知,然后可以选择采取一些行动。