F926e5fb0028f5b9763bd44bcc3aa880
Java 引用的四种写法

引用,一定是大家在 Java 中接触的最多的东西了。那么茴香豆...引用究竟有几种写法呢?

实际上 Java 是有四种引用的,分别是:强引用、弱引用、软引用和虚引用,其中大家最熟悉也最常接触到的一般都是强引用。那么这四种引用有什么不同呢?答案将在下文揭晓:

强引用

我们平时用到的最多的引用就是强引用。如果一个对象具有强引用,那么 GC 就不会回收它。

在下面的代码中我就定义了一个强引用:

Object strongRef = new Object();    // 强引用

顾名思义,强引用是很强势的。当虚拟机内存不足时,Java 虚拟机宁愿抛出 OutOfMemoryError 异常也不愿意回收具有强引用的对象。因此,当我们不使用一个对象时,应该用如下的方式来弱化引用,来辅助 GC 对内存进行回收。

strongRef = null;       // 辅助GC回收这个对象

当我们显式地设置 strongRef 为 null,或者这个对象超出了它的生命周期范围时,GC会认为该对象不存在引用。这时 GC 就会对这个对象的内存进行回收。但这里有一点需要注意:回收不是立即执行的!而具体何时回收取决于 GC 的算法。

接下来我们看到下面的这个例子:

public void function(){
    Object ref = new Object();
}

我们在 function 方法中有一个强引用 ref,它被存储于栈中。而真正 new 出来的对象则保存在堆中。当方法结束后,强引用的生命周期结束,此时引用不复存在,这个对象就会被回收。

而当 ref 为全局变量时,比较推荐的做法是在使用结束后将它置为 null,来辅助GC对其进行回收,避免内存的浪费。

在 ArrayList 类的源码中,就有一个 clear() 函数来辅助 GC 回收它内部的数据。下面是它的具体实现:

private transient Object[] elementData;
...
public void clear() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
}

可以看到,它内部执行的操作是将所有元素的引用全部置为 null,而不是仅仅把 elementData 置为 null。如果仅仅把 elementData 这个数组置为 null,强引用会仍然存在。当我们使用完数组元素时,采用类似上面代码的方法可以及时地释放它的内存。

软引用

采用软引用,当内存空间足够的时候, GC 不会回收它。而当内存空间不足时,GC 就会回收这些对象。只要软引用不被回收,就可以继续使用。

 Integer num = new Integer(1);                                          // 强引用
 SoftReference<Integer> softRef = new SoftReference<Integer>(num);      // 软引用

软引用在实际的应用场景中十分重要,就比如我们浏览器的后退按钮:当我们按下后退键时,加载前一个网页有两种策略可供选择:

  1. 将网页从缓存中重新取出
  2. 重新进行请求的加载

如果我们仅仅采用第一种策略,会造成大量的内存浪费,甚至内存溢出。

如果我们仅仅采用第二种策略,十分浪费网络资源,并且也会使得加载缓慢。

有了软引用,我们就有了一个更合适的折中方案:使用软引用来存放网页。这样当内存足够时,我们就可以直接从缓存中获取之前加载的网页。而内存不够时缓存自然地被释放,此时再从网络重新加载网页。

Website prevPage = new Website(url);               
SoftReference ref = new SoftReference(prevPage);    
... // 其他操作
if (ref.get()!=null) { 
    prevPage = (Browser) ref.get();          
} else {
    prevPage = new Website(url);               
    ref = new SoftReference(prevPage);       
}

弱引用

弱引用和软引用听起来十分相似,它们在效果上其实也非常相似。它们的区别仅仅在于:只具有弱引用的对象的生命周期更加短暂。

在 GC 线程扫描内存区域的过程中,一旦发现只具有弱引用的对象,无论当前内存空间足够与否,都对它的内存进行回收。不过由于 GC 线程是一个优先级很低的线程,因此一般不会很快发现那些只具有弱引用的对象。

String str = new String("abc");    
WeakReference<String> weakRef = new WeakReference<String>(str);
str = null; 

如果这个对象只是偶尔使用,并且我们希望随时都能获取到,又不想影响此对象的垃圾收集,那么就应该用弱引用存储此对象。

虚引用

虚引用和其他几种引用都不同,它是形同虚设的,并不会对对象的生命周期有实质性的影响。如果一个对象仅仅只有虚引用,在 GC 看来和没有引用实际上是一样的,这样的对象随时会被 GC 回收。一般使用虚引用主要是用于追踪对象被回收的过程。

总结

我们用下面的表格来对这篇文章进行总结:

引用类型 被垃圾回收时间 用途 生命周期
强引用 从来不会 对象的一般状态 JVM 停止运行时终止
软引用 在内存不足时 对象缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 GC 运行后终止
虚引用 未知 未知 未知
© 著作权归作者所有
这个作品真棒,我要支持一下!
专栏简介: 本专栏是一个免费专栏,旨在以 Android 开发小白的角度分享技术文章,希望能同本专栏的读者一同成...
0条评论
top Created with Sketch.