在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。
强引用、软引用、弱引用、虚引用
各引用的讲解
强引用
在平时编码中,Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
软引用
软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
弱应用
弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用同样可用于内存敏感的缓存。
虚引用
虚引用也称为幽灵引用或者幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
代码实现
public class Person {
private Integer id;
public Person(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
public class MainTest {
/**
* 软引用
*/
@Test
public void test1() {
//在堆中创建一个对象Obj
//在栈中创建一个p来强引用此对象Obj
Person p = new Person(1);
//在栈中创建一个softReference来软引用此对象Obj 可以获取对象的属性值
SoftReference<Person> softReference = new SoftReference<Person>(p);
System.out.println(p.getId());//输出打印:1
System.out.println(Objects.requireNonNull(softReference.get()).getId());//输出打印:1
//断开p和Obj的强引用
p = null;
//System.out.println(p.getId());
//System.gc();
System.out.println(Objects.requireNonNull(softReference.get()).getId());//输出打印:1
//并不报空指针异常 虽然断开了p和Obj的强引用,但是并没有被回收.
//如果在前面调用gc() 垃圾回收 运行结果也是打印1的..软引用只有系统在发生内存溢出异常之前,会把只被软引用的对象进行回收
}
/**
* 弱引用
*/
@Test
public void test2() {
//在堆中创建一个对象Obj
//在栈中创建一个p来强引用此对象Obj
Person p = new Person(1);
//在栈中创建一个weakReference来弱引用此对象Obj 可以获取对象的属性值
WeakReference<Person> weakReference = new WeakReference<Person>(p);
System.out.println(Objects.requireNonNull(weakReference.get()).getId());//打印输出:1
//断开p和Obj的强引用
p = null;
//System.gc();
System.out.println(Objects.requireNonNull(weakReference.get()).getId());//打印输出:1
//p=null 之后 还是可以正常的打印输出1 说明断开强引用和其他弱引用,软引用压根没有关系.
//如果在打印之前 调用gc() 方法之后 就会报错..java.lang.NullPointerException
//垃圾回收不论内存是否不足都会回收只被弱引用关联的对象。
}
/**
* 虚引用: 1
*/
@Test
public void test3() {
//在堆中创建一个对象Obj
//在栈中创建一个p来强引用此对象Obj
Person p = new Person(1);
//Phantom 幻影幽灵 的意思
ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
//在栈中创建一个phantomReference来虚引用此对象Obj 不可以获取对象的属性值
PhantomReference<Person> phantomReference = new PhantomReference<Person>(p, referenceQueue);
System.out.println(Objects.requireNonNull(phantomReference.get()).getId());//打印报错 java.lang.NullPointerException
//直接得不到p对象对应的id值....
//PhantomReference的唯一作用就是 能在这个对象被收集器回收时收到一个系统通知 看test4()方法
}
/**
* 虚引用: 2
*/
@Test
public void test4() {
//在堆中创建一个对象Obj
//在栈中创建一个p来强引用此对象Obj
Person p = new Person(1);
//Phantom 幻影幽灵 的意思
ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
//在栈中创建一个phantomReference来虚引用此对象Obj 不可以获取对象的属性值
PhantomReference<Person> phantomReference = new PhantomReference<Person>(p, referenceQueue);
System.out.println(referenceQueue.poll());//打印输出: null 这个是查询队列中是否有元素.
//断开p和obj的强引用
p = null;
System.gc();//p被回收之后 队列referenceQueue中就有值了.
System.runFinalization(); // 强制调用已经失去引用的对象的finalize方法
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}//过 一秒钟之后再查询队列中是否有元素.
System.out.println(referenceQueue.poll());//打印输出: java.lang.ref.PhantomReference@77fef1a0
//PhantomReference的唯一作用就是 能在这个对象被收集器回收时收到一个系统通知
//如果这个对象被回收了,会把通知放到队列中.
//如果前面p=null注释掉 再运行打印输出就是 null 因为p没有被回收(强引用中) 就不会把通知放到队列中...队列中为空 null
//回收的标志就是把通知放到队列中..
}
}