四、强引用、软引用、弱引用、幻象引用有什么区别

一、概念

  1. 强引用就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还"活着",垃圾收集器不会处理这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应引用赋值为null,就是可以被垃圾收集的了,当然具体回收时机还需要看垃圾收集策略。我们使用的大部分引用都是强引用,这是使用最普遍的引用。如:
String strongReference = new String("refenrence");

有一点需要注意的是像下面这种代码中list集合里的数据并不会进行释放,即使内存不足时也不会进行释放:

String strongReference = new String("refenrence");
List<String> list = new ArrayList<>();
list.add(strongReference);
list = null;
System.out.println(list);
System.out.println(strongReference);

这是因为ArrayList是使用数组来实现的,在ArrayList中定义了一个elementData[],如果没有指定ArrayList大小这个数组默认是为空的,只有在第一次添加元素时才会默认初始化容量为10,ArrayList类有一个java.util.ArrayList#clear方法用来清空集合中的元素,会将集合中的每个元素执行

elementData[i] = null;

不同于

list = null;

强引用仍然存在,避免在后续调用add()方法添加元素时进行重新的内存分配。使用如clear()方法释放内存的方法对数组中存放的引用类型非常适用,这样就可以及时释放内存。

  1. 软引用是一种相对弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时才会去试图回收软引用指向的对象。JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时就清理掉,这样就保证了使用缓存的同时,不会耗尽内存
String str = "软引用";
SoftReference softReference = new SoftReference(str);
str = null;
System.out.println(softReference.get());
System.gc();
System.runFinalization();
System.out.println(softReference.get());

上面的例子中两次都会正确输出"软引用",因为软引用只会在JVM认为内存不足时才会清理。

  1. 弱引用并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如维护一种非强制性的映射关系,如果试图获取时对象还在,就使用他否则就重新实例化。软引用适合做缓存而弱引用适合存储元数据
String str = new String("弱引用");
WeakReference weakReference = new WeakReference(str);
str = null;
System.out.println(weakReference.get());
System.gc();
System.runFinalization();
System.out.println(weakReference.get());

上面的例子中只会输出一次"弱引用",因为只要JVM运行GC发现有弱引用便会直接进行清理,并不会关注此时JVM内存是否充足。

  1. 幻象引用仅仅提供了一种确保对象被finalize之后做某些事情的机制,比如通常用来做所谓的post-Mortem清理机制,也有人利用幻想引用来监控对象的创建和销毁。
String str = new String("幻象引用");
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference(str, referenceQueue);
str = null;
System.out.println(phantomReference.get());
System.gc();
System.runFinalization();
System.out.println(referenceQueue.poll() == phantomReference);

幻想引用在任何时候使用get方法都只会返回null,因为幻想引用总是不可达的。需要注意的是幻象引用必须与引用队列结合起来使用,幻想引用会在对象被释放(执行finalize后)时加入到引用队列之中,软引用与弱引用是在对象被垃圾回收之后会被加入到引用队列中。

不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。

二、引用队列
我们在创建各种引用并关联到相应对象中时,可以选择是否需要关联引用队列,JVM会在特定时机将引用放入引用队列中,我们可以从队列中获取引用进行相关后续逻辑。尤其是幻想引用,get方法返回null,如果不指定引用队列基本就没有意义了。

Object counter = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
counter = null;
System.gc();
try {
    // Remove 是一个阻塞方法,可以指定 timeout,或者选择一直阻塞
    Reference<Object> ref = refQueue.remove(1000L);
    if (ref != null) {
        // do something
    }
} catch (InterruptedException e) {
    // Handle it
}

三、诊断JVM引用情况
如果怀疑应用存在引用导致的回收问题,可以通过指定JVM参数来排查诊断:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintReferenceGC

四、Reachability Fence
按照Java规范如果一个对象没有指向强引用就符合垃圾收集的标准,有些时候对象并没有强引用,但是也许它得部分属性还在被使用,这样就导致诡异的问题,所以我们需要一个方法,在没有强引用的情况下通知JVM对象是在被使用的。

class Resource {
 private static ExternalResource[] externalResourceArray = ...
 int myIndex; Resource(...) {
     myIndex = ...
     externalResourceArray[myIndex] = ...;
     ...
 }
 protected void finalize() {
     externalResourceArray[myIndex] = null;
     ...
 }
 public void action() {
 try {
     // 需要被保护的代码
     int i = myIndex;
     Resource.update(externalResourceArray[i]);
 } finally {
     // 调用 reachbilityFence,明确保障对象 strongly reachable
     Reference.reachabilityFence(this);
 }
 }
 private static void update(ExternalResource ext) {
    ext.status = ...;
 }
} 

方法action的执行依赖于对象的部分属性,所以被保护了起来。否则如果我们在代码中像这样调用

new Resource().action()

可能就会出现问题,如果没有强引用指向我们创建出来的Resource对象,JVM对它进行finalize是完全合法的。这在新的异步编程模式下可能会非常有用,可以保障对象不被意外回收。

五、项目或者系统中实际使用到引用的案例我暂时还没有遇到,等遇到的时候再来补充吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。