Skip to content

强引用、软引用、弱引用、虚引用 ​

Question ​

  • 谈谈 强引用、软引用、弱引用和虚引用 概念?
  • 为什么要有 强引用、软引用、弱引用和虚引用?
  • 关于 强引用、软引用、弱引用和虚引用 代码层面简单介绍?

引用回收、用途、生存时间 ​

引用类型被垃圾回收时间用途生存时间
强引用从来不会对象的一般状态JVM停止运行时终止
软引用在内存不足时对象缓存内存不足时终止
弱引用在垃圾回收时对象缓存gc运行后终止
虚引用垃圾回收时跟踪对象被垃圾回收的活动(回收时收到系统通知)不影响生存时间(任何时候都可能被回收)

引用类继承关系 ​

引用类继承关系

谈谈 强引用、软引用、弱引用和虚引用 概念 ​

JDK 1.2 版之后引入了软(SoftReference)、弱(WeakReference)、虚(PhantomReference)三种引用。FinalReference(强引用)

  • 强引用:最传统的「引用」的定义,是指在程序代码之中普遍存在的引用赋值。即类似Object obj=new Object()这种引用关系。只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

  • 软引用:描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中。系统第二次回收这些对象。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

  • 弱引用:描述那些非必须对象,但是它的强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  • 虚引用:是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的,是在这个对象被收集器回收时收到一个系统通知。

为什么要有 强引用、软引用、弱引用和虚引用? ​

从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。

Java中提供这四种引用类型主要有两个目的:

  • 可以让程序员通过代码的方式决定某些对象的生命周期

  • 有利于JVM垃圾回收。

关于 强引用、软引用、弱引用和虚引用 代码层面简单介绍? ​

强引用 ​

我们使用的大部分引用都是强引用,这是在我们的开发工作当中普遍存在的。如果一个对象具有强引用,那就类似我们经常生活中必不可少的用品。我们肯定不会扔掉,同样jvm的垃圾回收器也不会回收它。当内存空间不足的时候,java虚拟机宁可抛出OOM异常,也不会回收具有强引用的对象来释放内存。我们可以将对象显示的赋值为null,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

即方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。

但是如果这个对象是全局的变量时,就需要在不用这个对象时赋值为null。因为强引用不会被垃圾回收。

我们来看下Arraylist中的clear方法

java
/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

这里看到Arraylist的clear方法是把整个list中的所有对象都赋值成了null,然后就等到GC回收了。即栈中已经没有强引用指向的list中对象(分布在堆中)。

软引用 ​

软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。如果一个对象只有软引用,就类似鸡肋,食之无味、弃之可惜。如果内存空间足够大,垃圾回收期就不会回收它。如果内存空间不够了,就会回收这些对象。

只有垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用的对象被垃圾回收,java虚拟机会把这个软引用加入到与之关联的引用队列中。

java
String str=new String("abc");                                     // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用x
softRef.get() // 得到str对象,如果str被回收,则返回null

当内存不足时,等价于:

java
If(JVM.内存不足()) {
   str = null;  // 转换为软引用
   System.gc(); // 垃圾回收器进行回收
}

弱引用 ​

如果一个对象只有弱引用,那么就类似可有可无的生活用品。当JVM垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

弱引用和软引用的区别在于:弱引用的对象具有更短暂的生命周期。在垃圾回收时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,垃圾回收器是一个优先级很低的线程。因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用。如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入与之关联的引用队列。

java
import java.lang.ref.WeakReference;

public class WeakReferenceTest {

	public static void main(String[] args) {
		WeakReference<String> weakReference = new WeakReference<String>(new String("hello world"));
		System.out.println(weakReference.get());

		// 手工模拟JVM的gc进行垃圾回收
		System.gc();

		System.out.println(weakReference.get());
	}

}

输出结果:

java
hello world
null

这说明只要JVM开始垃圾回收,被弱引用关联的对象必定会被回收掉。

java
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;

public class ReferenceTest {

	private static ReferenceQueue<VeryBig> referenceQueue = new ReferenceQueue<VeryBig>();

	private static LinkedList<WeakReference<VeryBig>> weakReferencesList = new LinkedList<WeakReference<VeryBig>>();

	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			ReferenceTest referenceTest = new ReferenceTest();
			weakReferencesList.add(referenceTest.new VeryBigWeakReference(referenceTest.new VeryBig("Weak" + i), referenceQueue));
		}
		System.out.println("first Verbig:" + weakReferencesList.getFirst().get());

		// manual gc
		System.gc();

		// For a while, wait for the above code execution
		try {
			TimeUnit.SECONDS.sleep(6);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		checkQueue();
		System.out.println("first Verbig:" + weakReferencesList.getFirst().get());
	}

	public static void checkQueue() {
		Reference<? extends VeryBig> reference = null;
		while ((reference = referenceQueue.poll()) != null) {
			if (reference != null) {
				System.out.println("In queue:" + ((VeryBigWeakReference) (reference)).id);
			}
		}
	}

	class VeryBig {

		public String id;

		private byte[] byteArray = new byte[2 * 1024];

		public VeryBig(String id) {
			super();
			this.id = id;
		}

		@Override
		protected void finalize() throws Throwable {
			super.finalize();

			System.out.println("recycle verbig:" + id);
		}

	}

	class VeryBigWeakReference extends WeakReference<VeryBig> {

		private String id;

		public VeryBigWeakReference(VeryBig referent, ReferenceQueue<? super VeryBig> q) {
			super(referent, q);

			this.id = referent.id;
		}

		@Override
		protected void finalize() throws Throwable {
			super.finalize();

			System.out.println("recycle veryBigWeakReference:" + id);
		}

	}

}

输出结果:

java
first Verbig:com.demotransfer.reference.ReferenceTest$VeryBig@7852e922
recycle verbig:Weak0
recycle verbig:Weak2
recycle verbig:Weak1
In queue:Weak0
In queue:Weak1
In queue:Weak2
first Verbig:null

虚引用 ​

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

虚引用主要用来跟踪对象被垃圾回收的活动。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,先检查它是否还有虚引用。如果还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以判断引用队列中是 否已经加入了虚引用,以此了解被引用的对象是否将要被垃圾回收。程序如果发现一个虚引用已加入引用队列,就可以在所引用对象的内存回收前采取必要的行动。

垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

java
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;

public class PhantomReferenceTest {

	private static ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();

	public static void main(String[] args) {
		Object object = new Object();
		PhantomReference<Object> phantomReference = new PhantomReference<Object>(object, referenceQueue);

		object = null;

		// always returns null
		System.out.println(phantomReference.get());

		// returns whether it has been deleted from memory
		System.out.println(phantomReference.isEnqueued());

		// Manual gc
		System.gc();

		// For a while, wait for the above code execution
		try {
			TimeUnit.SECONDS.sleep(6);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println(phantomReference.isEnqueued());
	}

}

输出结果:

java
null
false
true