众所周知,Java是有四种引用的,分别是:强引用、弱引用、软引用、虚引用。
为了学习这些引用之间的区别和联系,写下这篇博客。
强引用
我们平时用到的最普遍的引用,就是强引用。如果一个对象具有强引用,GC就不会回收它。
比如下面就是一个强引用
Object o = new Object(); // 强引用
当我们内存不足时,Java虚拟机宁愿抛出OutOfMemoryError异常也不愿意回收具有强引用的对象。
因此,当我们不使用一个对象时,应该用如下方式来弱化引用,帮助GC回收
o = null; // 帮助GC回收这个对象
当我们显式地设置o为null,或者超出了对象的生命周期范围,GC会认为该对象不存在引用,这时就可以回收这个对象。但要注意的是,不一定是马上回收!具体何时回收取决于GC的算法。
比如我们下面的例子
public void function(){
Object o = new Object();
}
我们在方法中有一个强引用,它保存在栈中。而真正new出来的对象则保存在堆中。当方法结束,强引用的生命周期结束,此时引用不再存在,这个对象就会被回收。
当o为全局变量时,我们就需要在使用结束后将它置为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); // 软引用
软引用在实际的使用场景中十分重要,比如我们浏览器的后退按钮。当我们按下后退键时,加载前一个网页有两种策略
-
从缓存中重新取出
-
重新进行请求加载
如果我们仅仅采用第一种策略,会造成大量的内存浪费,甚至内存溢出。
如果我们仅仅采用第二种策略,会浪费网络资源,并且加载缓慢。
此时,我们就可以使用软引用来存放。
Browser prev = new Browser();
SoftReference ref = new SoftReference(prev);
if(ref.get()!=null){
rev = (Browser) ref.get();
}else{
prev = new Browser();
ref = new SoftReference(prev);
}
弱引用
弱引用与软引用的区别在于:只具有弱引用的对象的生命周期更加短暂。
在GC线程扫描它所的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
如果这个对象偶尔使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么应该用 Weak Reference 来存储此对象。
虚引用
虚引用和其他几种引用都不同,它是形同虚设的,并不会决定对象的声明周期。如果一个对象仅仅只有虚引用,和它没有引用是一样的,随时会被GC回收。
使用虚引用主要用于追踪对象被回收的过程。
总结
可以用下表来总结本篇文章
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | gc运行后终止 |
虚引用 | Unknown | Unknown | Unknown |