java中虚引用PhantomReference与弱引用WeakReference(软引用SoftReference)的差别

之前的这篇博客介绍了java中4种引用的差别和使用场景,在最后的总结中提到:

“软引用和弱引用差别不大,JVM都是先把SoftReference和WeakReference中的referent字段值设置成null,之后加入到引用队列;而虚引用则不同,如果某个堆中的对象,只有虚引用,那么JVM会将PhantomReference加入到引用队列中,JVM不会自动将referent字段值设置成null”。这段总结写的比较仓促,也没有给出实际的例子加以佐证。本文主要是重申下这几种引用的差别,并给出实际的例子,让读者清楚的感受到它们的差别。

软引用和弱引用差别不大,JVM都是先将其referent字段设置成null,之后将软引用或弱引用,加入到关联的引用队列中。我们可以认为JVM先回收堆对象占用的内存,然后才将软引用或弱引用加入到引用队列

而虚引用则不同,JVM不会自动将虚引用的referent字段设置成null,而是先保留堆对象的内存空间,直接将PhantomReference加入到关联的引用队列,也就是说如果我们不手动调用PhantomReference.clear(),虚引用指向的堆对象内存是不会被释放的。

referent是java.lang.ref.Reference类的私有字段,虽然没有暴露出共有API来访问这个字段,但是我们可以通过反射拿到这个字段的值,这样就能知道引用被加入到引用队列的时候,referent到底是不是null。SoftReference和WeakReference是一样的,这里我们以WeakReference为例。

package ref.referent;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

// 会报空指针:WeakReference中的referent被设置成null,之后加入到ReferenceQueue
public class TestWeakReference
{
	private static volatile boolean isRun = true;

	private static volatile ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();

	public static void main(String[] args) throws Exception
	{
		String abc = new String("abc");
		System.out.println(abc.getClass() + "@" + abc.hashCode());

		new Thread() {
			public void run()
			{
				while (isRun)
				{
					Object o = referenceQueue.poll();
					if (o != null)
					{
						try
						{
							Field rereferent = Reference.class
									.getDeclaredField("referent");
							rereferent.setAccessible(true);
							Object result = rereferent.get(o);
							System.out.println("gc will collect:"
									+ result.getClass() + "@"
									+ result.hashCode());
						} catch (Exception e)
						{
							e.printStackTrace();
						}
					}
				}
			}
		}.start();

		// 对象是弱可达的
		WeakReference<String> weak = new WeakReference<String>(abc,
				referenceQueue);
		System.out.println("weak=" + weak);

		// 清除强引用,触发GC
		abc = null;
		System.gc();

		Thread.sleep(3000);
		isRun = false;
	}
}

运行这段代码会发现,我们创建的Thread中报空指针异常。当我们清除强引用,触发GC的时候,JVM检测到new String("abc")这个堆中的对象只有WeakReference,那么JVM会释放堆对象的内存,并自动将WeakReference的referent字段设置成null,所以result.getClass()会报空指针异常。

代码与上面类似,
我们将WeakReference替换成PhantomReference:

package ref.referent;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;

// 当PhantomReference加入到ReferenceQueue的时候,目标对象内存空间仍然存在不会被回收.
// PhantomReference中的referent字段不会被JVM自动设置成null
// 当目标对象的PhantomReference加入到ReferenceQueue的时,此时目标对象是强可达的
public class TestPhantomReference
{
	private static volatile boolean isRun = true;

	private static volatile ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();

	public static void main(String[] args) throws Exception
	{
		String abc = new String("abc");
		System.out.println(abc.getClass() + "@" + abc.hashCode());

		new Thread() {
			public void run()
			{
				while (isRun)
				{
					Object o = referenceQueue.poll();
					if (o != null)
					{
						try
						{
							Field rereferent = Reference.class
									.getDeclaredField("referent");
							rereferent.setAccessible(true);
							Object result = rereferent.get(o);
							System.out.println("gc will collect:"
									+ result.getClass() + "@"
									+ result.hashCode());
						} catch (Exception e)
						{
							e.printStackTrace();
						}
					}
				}
			}
		}.start();

		// 测试情况1:对象是虚可达的
		PhantomReference<String> phantom = new PhantomReference<String>(abc,
				referenceQueue);
		System.out.println("phantom=" + phantom);

		// 测试情况2:对象是不可达的,直接就被回收了,不会加入到引用队列
		// new PhantomReference<String>(abc, referenceQueue);

		// 清除强引用,触发GC
		abc = null;
		System.gc();

		Thread.sleep(3000);
		isRun = false;
	}
}

运行这段代码会发现,程序没有报异常,执行结果是:

class [email protected]
[email protected]
gc will collect:class [email protected]

很明显,当PhantomReference加入到引用队列的时候,referent字段的值并不是null,而且堆对象占用的内存空间仍然存在。也就是说对于虚引用,JVM是先将其加入引用队列,当我们从引用队列删除PhantomReference对象之后(此时堆中的对象是unreachable的),那么JVM才会释放堆对象占用的内存空间。由此可见,使用虚引用有潜在的内存泄露风险,因为JVM不会自动帮助我们释放,我们必须要保证它指向的堆对象是不可达的。从这点来看,虚引用其实就是强引用,当内存不足的时候,JVM不会自动释放堆对象占用的内存。后续的帖子我会进行一些OOM相关的实验,去证明虚引用的确会导致OOM,而软引用和弱引用则不会导致OOM。

小结:

上面的测试代码,只是为了帮助我们看清楚虚引用与软引用/弱引用的不同表现。在实际的开发中,我们是不会通过反射获取referent字段的值,这样做毫无意义,也不值得提倡。

时间: 2024-10-26 20:32:23

java中虚引用PhantomReference与弱引用WeakReference(软引用SoftReference)的差别的相关文章

java 谈谈引用(强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference))

简单谈谈引用(摘自java虚拟机第二版 ) 署名:wander   一.四种引用 在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference).软引用(Soft Reference).弱引用(Weak Reference).虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱. 二.引用介绍及回收时机 1.强引用 >>> 就是指在程序代码之中普遍存在的,类似"Object obj=new Object()&q

浅谈C语言中的强符号、弱符号、强引用和弱引用

摘自http://www.jb51.net/article/56924.htm 浅谈C语言中的强符号.弱符号.强引用和弱引用 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014-10-31 我要评论 这篇文章主要介绍了C语言中的强符号.弱符号.强引用和弱引用的定义及相关内容,非常的简单易懂,有需要的朋友可以参考下 首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑

关于C++与Java中虚函数问题的读书笔记

之前一直用C++编程,对虚函数还是一些较为肤浅的理解.可近期由于某些原因搞了下Java,发现有些知识点不熟,于是站在先驱巨人的肩上谈谈C++与Java中虚函数问题. Java中的虚函数 以下是段别人的代码,输入结果竟是Base,这让一直以来用C/C++的我有些莫不着头脑,不是Java里对象是引用吗?C/C++中通过指向基类的指针或引用来指向派生类,那么对于虚函数来说通过基类指针使用的是指向的派生类.但在Java中没有keyword标明虚函数,此时就不是非常明确究竟调用的谁. class base

关于C语言中的强符号、弱符号、强引用和弱引用的一些陋见,欢迎指正

首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑,所以写下此篇,希望能和同样感觉的朋友交流也希望高人指点. 首先我们看一下书中关于它们的定义. 引入场景:(1)文件A中定义并初始化变量i(int i = 1), 文件B中定义并初始化变量i(int i = 2).编译链接A.B时会报错b.o:(.data+0x0): multiple definition of `i':a.o:(.d

java中方法的参数传递机制(值传递还是引用传递)

看到一个java面试题: 问:当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?  答:是值传递.Java 编程语言只有值传递参数.当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本.指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的. 以下是从其他文章里转的,只为加深理解 public class TempTest { private void te

Java中的四种引用类型,强引用,软引用,弱引用,虚引用

对于Java中的垃圾回收机制来说,对象是否被回收的标准在于该对象是否被引用.因此,引用也是JVM进行内存管理的一个重要概念. Java中对象的引用一般有以下4种类型: 1强引用  2软引用  3弱引用  4虚引用 以下一一介绍其用法和区别 1强引用:在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用.当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收.因此强引用是造成Java内存泄漏的主

Java中的强引用、软引用、弱引用和虚引用

本文为阅读下面四篇博文的读书笔记 http://sishuok.com/forum/blogPost/list/342.html http://blog.sae.sina.com.cn/archives/5228?utm_source=tuicool http://www.cnblogs.com/dolphin0520/p/3784171.html http://blog.csdn.net/arui319/article/details/8489451 四种引用的前世今生 Java 在设计之初就

Java中的四种引用(强引用、软引用、弱引用、虚引用)

以下内容摘自<深入理解Java虚拟机 JVM高级特性与最佳实践>第2版,强烈推荐没有看过的同学阅读,读完的感觉就是"原来学的都是些什么瘠薄东西(╯‵□′)╯︵┴─┴" 在JDK1.2以前,Java中的引用的定义很传统:如果 reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用.这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些"食之无味,弃之可惜"的对象就显得

Java 强引用、 软引用、 弱引用、虚引用

 1.对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于可触及(reachable)状态,程序才能使用它.从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期.这4种级别由高到低依次为:强引用.软引用.弱引用和虚引用.下图为对象应用类层次. ⑴强引用(StrongReference)   强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内