JAVA 中的对象的软、弱和虚引用

对象与垃圾回收 :https://blog.****.net/qq_40794973/article/details/87740222


对大部分对象而言,程序里会有一个引用变量引用该对象,这是最常见的引用方式。除此之外,ava.lang.ref 包下提供了3个类:SoftReferencePhantomReferenceWeakReference,它们分别代表了系统对对象的3种引用方式:软引用虚引用弱引用。因此,Java语言对对象的引用有如下4种方式。

这是Java程序中最常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象,前面介绍的对象和数组都采用了这种强引用的方式。当一个对象被个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。 

软引用需要通过 SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它软引用通常用于对内存敏感的程序中。  

弱引用通过 WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收——正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收 。

  1. 强引用(StrongReference)
  2. 软引用(SoftReference)
  3. 弱引用(WeakReference)
  4. 虚引用(Phantom Reference)

虚引用通过 PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用。

上面三个引用类都包含了一个 get() 方法,用于获取被它们所引用的对象。

引用队列由 java.lang.ref.ReferenceQueue 类表示,它用于保存被回收后对象的引用。当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后,将把被回收对象对应的引用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动

软引用和弱引用可以单独使用,但虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解虚引用所引用的对象是否即将被回收。

下面程序示范了弱引用所引用的对象被系统垃圾回收过程。

import java.lang.ref.*;
public class ReferenceTest {
	public static void main(String[] args)
			throws Exception
	{
		// 创建一个字符串对象
		String str = new String("疯狂Java讲义");
		// 创建一个弱引用,让此弱引用引用到"疯狂Java讲义"字符串
		WeakReference wr = new WeakReference(str);  //①
		// 切断str引用和"疯狂Java讲义"字符串之间的引用
		str = null;   //②
		// 取出弱引用所引用的对象
		System.out.println(wr.get());  //③
		// 强制垃圾回收
		System.gc();
		System.runFinalization();
		// 再次取出弱引用所引用的对象
		System.out.println(wr.get());  //④
	}
}

疯狂Java讲义
null

上面程序先创建了一个“疯狂Java讲义”字符串对象,并让str引用变量引用它,执行①行粗体字代码时,系统创建了一个弱引用对象,并让该对象和 str 引用同一个对象。当程序执行到②行代码时,程序切断了str和“疯狂Java讲义”字符串对象之间的引用关系。此时系统内存如图6.10所示。

JAVA 中的对象的软、弱和虚引用

 提示:

编译上面程序时会出现一个警告提示,这个警告提示是一个泛型提示。此处先不要理它。不仅如此,上面程序创建“疯狂Java讲义”字符串对象时,不要使用 String str=“疯狂Java讲义”,否则将看不到运行效果。因为采用 String str=“疯狂Java讲义”;代码定义字符串时,系统会使用常量池来管理这个字符串直接量(会使用强引用来引用它),系统不会回收这个字符串直接量。

 当程序执行到③号粗体字代码时,由于本程序不会导致内存紧张,此时程序通常还不会回收弱引用 wr 所引用的对象,因此在③号代码处可以看到输出“疯狂Java讲义”字符串。

执行到③号粗体字代码之后,程序调用了 System.gc();和 System.runFinalization();通知系统进行垃圾回收,如果系统立即进行垃圾回收,那么就会将弱引用 wr 所引用的对象回收。接下来在④号粗体字代码处将看到输出 null 。

 

下面程序与上面程序基本相似,只是使用了虚引用来引用字符串对象,虚引用无法获取它引用的对象。下面程序还将虚引用和引用队列结合使用,可以看到被虚引用所引用的对象被垃圾回收后,虚引用将被添加到引用队列中。

import java.lang.ref.*;
public class PhantomReferenceTest {
	public static void main(String[] args)
			throws Exception
	{
		// 创建一个字符串对象
		String str = new String("疯狂Java讲义");
		// 创建一个引用队列
		ReferenceQueue rq = new ReferenceQueue();
		// 创建一个虚引用,让此虚引用引用到"疯狂Java讲义"字符串
		PhantomReference pr = new PhantomReference (str , rq);
		// 切断str引用和"疯狂Java讲义"字符串之间的引用
		str = null;
		// 取出虚引用所引用的对象,并不能通过虚引用获取被引用的对象,所以此处输出null
		System.out.println(pr.get());  //①
		// 强制垃圾回收
		System.gc();
		System.runFinalization();
		// 垃圾回收之后,虚引用将被放入引用队列中
		// 取出引用队列中最先进入队列中的引用与pr进行比较
		System.out.println(rq.poll() == pr);   //②
	}
}

null
true

因为系统无法通过虚引用来获得被引用的对象,所以执行①处的输出语句时,程序将输出null(即使此时并未强制进行垃圾回收)。当程序强制垃圾回收后,只有虚引用引用的字符串对象将会被垃圾回收,当被引用的对象被回收后,对应的虚引用将被添加到关联的引用队列中,因而将在②代码处看到输出true。

使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方式引用对象,垃圾回收器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小时,这些引用类就很有用处。

必须指出:要使用这些特殊的引用类,就不能保留对对象的强引用;如果保留了对对象的强引用,就会浪费这些引用类所提供的任何好处。

由于垃圾回收的不确定性,当程序希望从软、弱引用中取出被引用对象时,可能这个被引用对象已经被释放了。如果程序需要使用那个被引用的对象,则必须重新创建该对象。这个过程可以采用两种方式完成,下面代码显示了其中一种方式

 JAVA 中的对象的软、弱和虚引用

 JAVA 中的对象的软、弱和虚引用

 下面代码显示了另一种取出被引用对象的方式。

JAVA 中的对象的软、弱和虚引用

上面两段代码采用的都是伪码,其中 recreateIt() 方法用于生成一个obj对象。这两段代码都是先判断obj对象是否已经被回收,如果已经被回收,则重新创建该对象。如果弱引用引用的对象已经被垃圾回收释放了,则重新创建该对象。但第一段代码存在一定的问题:当 if 块执行完成后,obj 还是有可能为null。因为垃圾回收的不确定性,假设系统在①和②行代码之间进行垃圾回收,则系统会再次将 wr 所引用的对象回收,从而导致 obj 依然为null。第二段代码则不会存在这个问题,当 if 块执行结束后,obj一定不为 null 。