基本概念
从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。下面来阐述一下这四种类型引用的概念:
1、强引用(StrongReference)
强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:
Object object = new Object();
String str = "hello";
只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。比如下面这段代码:
public class Main {
public static void main(String[] args) {
new Main().fun1();
}
public void fun1() {
Object object = new Object();
Object[] objArr = new Object[1000];
}
}
当运行至Object[] objArr = new Object[1000];这句时,如果内存不足,JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是,当fun1运行完之后,object和objArr都已经不存在了,所以它们指向的对象都会被JVM回收。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
比如Vector类的clear方法中就是通过将引用赋值为null来实现清理工作的:
2、软引用(SoftReference)
软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
import java.lang.ref.SoftReference;
public class Main {
public static void main(String[] args) {
// 获取页面进行浏览,Browser虚拟示例对象,实际不存在
Browser prev = new Browser();
// 浏览完毕后置为软引用
SoftReference<Browser> sr = new SoftReference<>(prev);
// 还没有被回收器回收,直接获取
if(sr.get()!=null){
rev = (Browser) sr.get();
}else{
// 由于内存吃紧,所以对软引用的对象回收,重新构建
sr = new SoftReference(new Browser());
}
}
}
3、弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。
下面的代码会让str再次变为一个强引用:
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
String abc = abcWeakRef.get();
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
public class ReferenceTest {
private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<>();
public static void checkQueue() {
Reference<? extends VeryBig> ref;
// 获取gc回收信息引用队列中的弱引用
while ((ref = rq.poll()) != null) {
System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).id);
}
}
public static void main(String[] args) {
int size = 3;
// 创建3个VeryBigWeakReference弱引用的VeryBig
LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<>();
for (int i = 0; i < size; i++) {
// 创建弱引用对象,并指定回收引用队列
weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
System.out.println("Just created weak: " + weakList.getLast());
}
// 通知系统gc回收
System.gc();
try {
// 下面休息6秒,让上面的垃圾回收线程运行完成(其实不需要,就是保证准确)
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 查看ReferenceQueue储存的信息
checkQueue();
}
}
/**
* 弱引用对象
*/
class VeryBig {
public String id;
// 占用空间,让线程进行回收
byte[] b = new byte[2 * 1024];
public VeryBig(String id) {
this.id = id;
}
/**
* gc收回时会自动调用finalize方法
*/
@Override
protected void finalize() {
System.out.println("Finalizing VeryBig " + id);
}
}
/**
* 创建指定功能的弱引用类
*/
class VeryBigWeakReference extends WeakReference<VeryBig> {
public String id;
/**
* 创建弱引用对象,并指定回收引用队列
* @param big 弱引用对象
* @param rq 回收引用队列
*/
public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
super(big, rq);
this.id = big.id;
}
/**
* gc收回时会自动调用finalize方法
*/
@Override
protected void finalize() {
System.out.println("Finalizing VeryBigWeakReference " + id);
}
}
执行结果:
4、虚引用(PhantomReference)
如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Main {
public static void main(String[] args) {
// 创建引用队列
ReferenceQueue<String> queue = new ReferenceQueue<>();
// 创建虚引用并指定引用队列
PhantomReference<String> pr = new PhantomReference<>(new String("hello"), queue);
System.out.println(pr.get());
}
}