回过头来看对象的四种状态强软弱虚引用的理解

一、对象的四种引用状态描述

  在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4中引用强度一次减弱。

  • 强引用就是指在程序代码之中普遍存在的,类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
  • 软引用是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收【内存不GC 不进行回收】。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用
  • 虚引用也成为幽灵引用或者幻影引用,它是最弱的一中引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供给了PhantomReference类来实现虚引用

JVM参数初始化设置 便于代码演示

在通过代码研究几种引用状态之前,先定义一些参数,后面所有部分的代码示例都使用这些参数。

首先是JVM的参数,这里我使用的是:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseParNewGC -verbose:gc -XX:+PrintGCDetails

这意味着:

  • 堆大小固定为20M
  • 新生代大小为10M,SurvivorRatio设置为8,则Eden区大小=8M,每个Survivor区大小=1M,每次有9M的新生代内存空间可用来new对象
  • 新生代使用使用ParNew收集器,Server模式下默认是Parallel收集器,不过这个收集器的GC日志我看着没有ParNew收集器的GC日志舒服,因此就改成ParNew收集器了
  • 当发生GC的时候打印GC的简单信息,当程序运行结束打印GC详情

其次,再定义一个常量类"_1MB":

/**
 * 1M
 */
private static final int _1MB = 1024 * 1024;

代码示例使用byte数组,每个byte为1个字节,因此定义一个"_1MB"的常量就可以方便得到1M、2M、3M...的内存空间了。

强引用的研究

关于强引用的研究,研究的重点在于验证"当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用或理解为不可达的(要被回收)"这句话的正确性。虚拟机参数上面列了,首先写一个空方法:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testStrongReference0() {
 6 
 7 }

程序运行结果为:

 1 Heap
 2  par new generation   total 9216K, used 3699K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 3   eden space 8192K,  45% used [0x00000000f9a00000, 0x00000000f9d9cdc0, 0x00000000fa200000)
 4   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 5   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
 6  tenured generation   total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
 7    the space 10240K,   0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000)
 8  compacting perm gen  total 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
 9    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb243d88, 0x00000000fb243e00, 0x00000000fc2c0000)
10 No shared spaces configured.

这意味着新生代中本身就有3699K的内存空间。很好理解,因为虚拟机启动的时候就会加载一部分数据到内存中,这部分数据的大小为3699K。

下一步我们放一个4M的byte数组进去(4M是因为找一个相对大点的数字,结果会比较明显),4M=4096K,加上原来的3966K等于8062K。对这8062K内存空间触发一次GC:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testStrongReference0() {
 6     System.out.println("**********强引用测试(放一个4M的数组,触发GC)**********");
 7         
 8     byte[] bytes = new byte[4 * _1MB];
 9         
10     // 手动触发GC
11     System.gc();
12 }

运行结果为:

 1 **********强引用测试(放一个4M的数组,触发GC)**********
 2 [Full GC[Tenured: 0K->5161K(10240K), 0.0085630 secs] 7958K->5161K(19456K), [Perm : 4354K->4354K(21248K)], 0.0086002 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 3 Heap
 4  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 5   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47300, 0x00000000fa200000)
 6   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 7   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
 8  tenured generation   total 10240K, used 5161K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
 9    the space 10240K,  50% used [0x00000000fa400000, 0x00000000fa90a548, 0x00000000fa90a600, 0x00000000fae00000)
10  compacting perm gen  total 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
11    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb243dc0, 0x00000000fb243e00, 0x00000000fc2c0000)
12 No shared spaces configured.

总结一下这次GC的结果(由于这篇不是研究内存分配的文章,因此我们只关注结果,至于过程到底为什么就不细究了):

  • 新生代中只留下了284K大小的对象
  • 5161K大小的对象被移到了老年代中
  • 7958K大小的对象被进行了一次回收,剩余5161K大小的对象,相当于这一次的内存收集 清理掉了 7958 - 5161 = 1797K 空间

总结起来就是4M的byte数组并没有被回收(因为总共有5161K的对象,虚拟机启动的时候也才加载了3699K不到5161K,那4M的byte数组肯定是在的),原因是有bytes引用指向4M的byte数组。既然如此,我们把bytes置空看看结果如何:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testStrongReference0() {
 6     System.out.println("**********强引用测试(放一个4M的数组,bytes置空,触发GC)**********");
 7         
 8     byte[] bytes = new byte[4 * _1MB];
 9         
10     bytes = null;
11         
12     // 手动触发GC
13     System.gc();
14 }

运行结果为:

 1 **********强引用测试(放一个4M的数组,bytes置空,触发GC)**********
 2 [Full GC[Tenured: 0K->1064K(10240K), 0.0096213 secs] 7958K->1064K(19456K), [Perm : 4354K->4354K(21248K)], 0.0096644 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
 3 Heap
 4  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 5   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47300, 0x00000000fa200000)
 6   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 7   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
 8  tenured generation   total 10240K, used 1064K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
 9    the space 10240K,  10% used [0x00000000fa400000, 0x00000000fa50a368, 0x00000000fa50a400, 0x00000000fae00000)
10  compacting perm gen  total 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
11    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb243dc0, 0x00000000fb243e00, 0x00000000fc2c0000)
12 No shared spaces configured.

从GC详情我们可以看到:

  • 老年代只使用了1064K大小的内存
  • 新生代只使用了284K大小的内存

很显然4M的byte数组被回收

由这个例子我们回顾可以作为GC Roots的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,比如在方法中定义"Object obj = new Object();"
  • 方法区中类静态属性引用的对象,比如在类中定义"private static Object lock = new Object();",将Object对象作为一个锁,所有类共享
  • 方法区中常量引用的对象,比如在接口中定义"public static final char c = 'a';",字符'a'是一个常量
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象,这个不好找例子

这次的回收正是因为第一条。本身有bytes(在虚拟机栈中)指向4M的byte数组,由于将bytes置空。因此4M的byte数组此时没有任何一个可以作为GC Roots对象的引用指向它,即4M的byte数组被虚拟机标记为可回收的垃圾,在GC时被回收。

稍微扩展一下,这里上面代码的做法是手动将bytes置空,其实方法调用结束也是一样的,栈帧消失,栈帧消失意味着bytes消失,那么4M的byte数组同样没有任何一个可以作为GC Roots对象的引用指向它,因此方法调用结束之后,4M的byte数组同样会被虚拟机标记为可回收的垃圾,在GC时被回收。

软引用的研究

软引用之前说过了,JDK提供了SoftReference类共开发者使用,那我们就利用SoftReference研究一下软引用,测试代码为:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testSoftReference0() {
 6     System.out.println("**********软引用测试**********");
 7         
 8     byte[] bytes = new byte[4 * _1MB];
 9     SoftReference<byte[]> sr = new SoftReference<byte[]>(bytes);
10     System.out.println("GC前:" + sr.get());
11         
12     bytes = null;
13         
14     System.gc();
15     System.out.println("GC后:" + sr.get());
16 }

同样的new一个4M的byte数组,通过SoftReference构造方法放到SoftReference中。

这段代码最值得注意的是第9行"bytes=null"这一句,如果不将bytes置空,那么4M的byte数组还与强引用关联着,内存不够虚拟机将抛出异常而不会尝试回收它;将bytes置空则不一样,4M的byte数组失去了强引用,但是它又在SoftReference中,这意味着这个4M的byte数组目前仅仅与软引用关联

运行一下程序,结果为:

 1 **********软引用测试**********
 2 GC前:[B@76404629
 3 [Full GC[Tenured: 0K->5161K(10240K), 0.0094088 secs] 7953K->5161K(19456K), [Perm : 4354K->4354K(21248K)], 0.0094428 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
 4 GC后:[B@76404629
 5 Heap
 6  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 7   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47330, 0x00000000fa200000)
 8   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 9   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
10  tenured generation   total 10240K, used 5161K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
11    the space 10240K,  50% used [0x00000000fa400000, 0x00000000fa90a778, 0x00000000fa90a800, 0x00000000fae00000)
12  compacting perm gen  total 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
13    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb243f10, 0x00000000fb244000, 0x00000000fc2c0000)
14 No shared spaces configured.

看到GC前后,bytes都是"[B@76404629",很显然4M的byte数组并没有被回收。从内存空间来看,老年代中使用了5161K,和之前强引用测试是一样的,证明了这一点。

那我们怎么能看到弱引用的回收呢?既然弱引用是发生在内存不够之前,那只需要不断实例化byte数组,然后将之与软引用关联即可,代码为:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testSoftReference1() {
 6     System.out.println("**********软引用测试**********");
 7             
 8     SoftReference<byte[]> sr0 = new SoftReference<byte[]>(new byte[4 * _1MB]);
 9     SoftReference<byte[]> sr1 = new SoftReference<byte[]>(new byte[4 * _1MB]);
10     SoftReference<byte[]> sr2 = new SoftReference<byte[]>(new byte[4 * _1MB]);
11     SoftReference<byte[]> sr3 = new SoftReference<byte[]>(new byte[4 * _1MB]);
12     SoftReference<byte[]> sr4 = new SoftReference<byte[]>(new byte[4 * _1MB]);
13     SoftReference<byte[]> sr5 = new SoftReference<byte[]>(new byte[4 * _1MB]);
14             
15     System.out.println(sr0.get());
16     System.out.println(sr1.get());
17     System.out.println(sr2.get());
18     System.out.println(sr3.get());
19     System.out.println(sr4.get());
20     System.out.println(sr5.get());
21 }

运行结果为:

 1 **********软引用测试**********
 2 [GC[ParNew: 7958K->1024K(9216K), 0.0041103 secs] 7958K->5187K(19456K), 0.0041577 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] 
 3 [GC[ParNew: 5203K->331K(9216K), 0.0036532 secs] 9366K->9481K(19456K), 0.0036694 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 4 [GC[ParNew: 4427K->4427K(9216K), 0.0000249 secs][Tenured: 9149K->9149K(10240K), 0.0054937 secs] 13577K->13246K(19456K), [Perm : 4353K->4353K(21248K)], 0.0055600 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 5 [Full GC[Tenured: 9149K->783K(10240K), 0.0071252 secs] 13246K->783K(19456K), [Perm : 4353K->4352K(21248K)], 0.0071560 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
 6 [GC[ParNew: 4096K->41K(9216K), 0.0010362 secs] 4879K->4921K(19456K), 0.0010745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 7 [GC[ParNew: 4137K->10K(9216K), 0.0009216 secs] 9017K->8986K(19456K), 0.0009366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 8 null
 9 null
10 null
11 [B@4783165b
12 [B@6f30d50a
13 [B@6ef2bc8d
14 Heap
15  par new generation   total 9216K, used 4307K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
16   eden space 8192K,  52% used [0x00000000f9a00000, 0x00000000f9e32560, 0x00000000fa200000)
17   from space 1024K,   1% used [0x00000000fa200000, 0x00000000fa202978, 0x00000000fa300000)
18   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
19  tenured generation   total 10240K, used 8975K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
20    the space 10240K,  87% used [0x00000000fa400000, 0x00000000facc3f40, 0x00000000facc4000, 0x00000000fae00000)
21  compacting perm gen  total 21248K, used 4366K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
22    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb2439e0, 0x00000000fb243a00, 0x00000000fc2c0000)
23 No shared spaces configured.

从第8行~第13行的结果来看,前三个4M的byte数组被回收了,后三个4M的byte数组还在,这就证明了"被软引用关联的对象会在内存不够时被回收"。

这段代码我们可以做一个对比思考:

  • 如果4M的byte数组没有被软引用关联而是被强引用关联,且不释放强引用,那么new到第4个4M的byte数组时就会报错,因为老年代总共只有10M,前两个4M的byte数组可以进入老年代,第3个4M的byte数组new出来的时候放入新生代,但是当第四个4M的byte数组new出来的时候,第3个4M的byte数组却没法进入老年代(因为3个4M=12M,大于老年代的10M),虚拟机抛出OutOfMemoryError
  • 如果4M的byte数组被软引用关联且强引用已经释放,那么可以无限写"SoftReference<byte[]> sr = new SoftReference<byte[]>(new byte[4 * _1MB]);"这句代码,因为内存不够了就回收4M的byte数组,永远没有内存溢出的可能

所以,很多时候对一些非必需的对象,我们可以将直接将其与软引用关联,这样内存不够时会先回收软引用关联的对象而不会抛出OutOfMemoryError,毕竟抛出OutOfMemoryError意味着整个应用将停止运行。

弱引用的研究

JDK给我们提供的了WeakReference用以将一个对象关联到弱引用,弱引用的测试比较简单,代码为:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testWeakReference() {
 6     System.out.println("**********弱引用测试**********");
 7         
 8     WeakReference<byte[]> wr = new WeakReference<byte[]>(new byte[4 * _1MB]);
 9     System.out.println("GC前:" + wr.get());
10         
11     System.gc();
12     System.out.println("GC后:" + wr.get());
13 }

我这里不定义一个强引用直接关联4M的byte数组(避免忘了将对象与强引用的关联取消),这也是使用SoftReference、WeakReference时我个人比较推荐的做法。程序运行的结果为:

 1 **********弱引用测试**********
 2 GC前:[B@21dd63a8
 3 [Full GC[Tenured: 0K->1065K(10240K), 0.0080353 secs] 7958K->1065K(19456K), [Perm : 4353K->4353K(21248K)], 0.0080894 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 4 GC后:null
 5 Heap
 6  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 7   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47318, 0x00000000fa200000)
 8   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 9   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
10  tenured generation   total 10240K, used 1065K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
11    the space 10240K,  10% used [0x00000000fa400000, 0x00000000fa50a6e8, 0x00000000fa50a800, 0x00000000fae00000)
12  compacting perm gen  total 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
13    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb243dc8, 0x00000000fb243e00, 0x00000000fc2c0000)
14 No shared spaces configured.

看到GC后bytes为null了,且新生代、老年代中也没见到有4M以上的大对象,从两个角度都证明了,GC之后4M的byte数组被回收了。

Reference与ReferenceQueue

前面用代码验证了强引用、软应用、弱引用三种引用状态,虚引用就不演示了,记住虚引用是用于跟踪对象的回收状态就够了。

下面再讲一个知识点ReferenceQueue;

ReferenceQueue的作用分点讲解下:

  1. SoftReference、WeakReference、PhantomReference,在构造的时候可以通过构造函数传入一个ReferenceQueue,但是只有PhantomReference,ReferenceQueue是必须的
  2. 以SoftReference为例,一个类型为SoftReference的sr关联了一个4M的byte数组,那么当内存不够的时候,回收此4M的byte数组,sr.get()为null,表示sr不再关联此4M的byte数组
  3. 当sr对应的4M的byte数组被回收之后,sr本身被加入ReferenceQueue中,表示此软引用关联的对象被回收
  4. ReferenceQueue本身是一个Queue,可通过poll()方法不断拿到队列的头元素,如果是null表示没有 被回收的软引用关联的对象,如果不是null表示有 软引用关联的对象被回收
  5. SoftReference是这样的,WeakReference与PhantomReference同理

讲完理论,用代码验证一下,还是使用软引用,不过为了显示更清楚把GC显示相关参数(-verbose:gc  -XX:+PrintGCDetails)去掉,代码为:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testReferenceQueue() {
 6     System.out.println("**********引用队列测试**********\n");
 7         
 8     ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<byte[]>();
 9         
10     SoftReference<byte[]> sr0 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
11     SoftReference<byte[]> sr1 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
12     SoftReference<byte[]> sr2 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
13     SoftReference<byte[]> sr3 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
14     SoftReference<byte[]> sr4 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
15     SoftReference<byte[]> sr5 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);
16         
17     System.out.println("**********软引用关联的对象展示**********");
18     System.out.println(sr0 + "---" + sr0.get());
19     System.out.println(sr1 + "---" + sr1.get());
20     System.out.println(sr2 + "---" + sr2.get());
21     System.out.println(sr3 + "---" + sr3.get());
22     System.out.println(sr4 + "---" + sr4.get());
23     System.out.println(sr5 + "---" + sr5.get());
24         
25     System.out.println("**********引用队列中的SoftReference展示**********");
26     System.out.println(referenceQueue.poll());
27     System.out.println(referenceQueue.poll());
28     System.out.println(referenceQueue.poll());
29     System.out.println(referenceQueue.poll());
30     System.out.println(referenceQueue.poll());
31     System.out.println(referenceQueue.poll());
32 }

运行结果为:

 1 **********引用队列测试**********
 2 
 3 **********软引用关联的对象展示**********
 4 java.lang.ref.SoftReference@50ed0a5---null
 5 java.lang.ref.SoftReference@fa4033b---null
 6 java.lang.ref.SoftReference@58d01e82---null
 7 java.lang.ref.SoftReference@4783165b---[B@6f30d50a
 8 java.lang.ref.SoftReference@6ef2bc8d---[B@23905e3
 9 java.lang.ref.SoftReference@6db17b38---[B@1f10d1cb
10 **********引用队列中的SoftReference展示**********
11 java.lang.ref.SoftReference@50ed0a5
12 java.lang.ref.SoftReference@fa4033b
13 java.lang.ref.SoftReference@58d01e82
14 null
15 null
16 null

看到由于内存不够,回收了前三个软引用,且回收的三个软引用内存地址都能对得上,这证明了上面的说法。

虚引用的研究

本来不准备写虚引用的,因为比较简单,虚引用唯一的目的只是跟踪对象的回收。不过后面有网友朋友提出了问题,我自己看了一下,虚引用确实有一个容易混淆的地方,因此文章更新,最后一部分加上虚引用。

测试代码为:

 1 /**
 2  * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7082471.html
 3  */
 4 @Test
 5 public void testPhantomReference() {
 6     System.out.println("**********虚引用测试**********");
 7         
 8     ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<byte[]>();
 9         
10     byte[] bytes = new byte[4 * _1MB];
11     PhantomReference<byte[]> pr = new PhantomReference<byte[]>(bytes, referenceQueue);
12         
13     bytes = null;
14         
15     System.gc();
16         
17     System.out.println(referenceQueue.poll());
18     JdkUtil.sleep(100);
19     System.out.println(referenceQueue.poll());
20 }

第13行bytes置空,此时4M的byte数组只关联了虚引用。这里的重点代码是第17行~第19行三行,代码运行结果为:

 1 **********虚引用测试**********
 2 [Full GC[Tenured: 0K->5163K(10240K), 0.0108148 secs] 7795K->5163K(19456K), [Perm : 4355K->4355K(21248K)], 0.0108713 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
 3 null
 4 java.lang.ref.PhantomReference@6f30d50a
 5 Heap
 6  par new generation   total 9216K, used 365K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
 7   eden space 8192K,   4% used [0x00000000f9a00000, 0x00000000f9a5b768, 0x00000000fa200000)
 8   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
 9   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
10  tenured generation   total 10240K, used 5163K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
11    the space 10240K,  50% used [0x00000000fa400000, 0x00000000fa90acf8, 0x00000000fa90ae00, 0x00000000fae00000)
12  compacting perm gen  total 21248K, used 4371K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
13    the space 21248K,  20% used [0x00000000fae00000, 0x00000000fb244e98, 0x00000000fb245000, 0x00000000fc2c0000)
14 No shared spaces configured.

看到第一次从队列中取没有取到虚引用,休眠100ms之后却从队列中取到了虚引用,且GC输出也看得出来,新生代和老年代中没有大对象了,说明4M的byte数组确实被回收。

讲这个问题,首先看一下JDK API对于PhantomReference的描述:

注意红字,对象是虚可达到对象,对象在那时或者在以后的某一时间,它会将该引用加入队列,那么什么时候加入该引用队列?答案是被虚引用关联的对象真正被回收的时候

之前我们说了,虚引用唯一的作用是用于跟踪对象的垃圾回收的,System.gc()方法调用的时候,4M的byte数组并没有被马上回收,System.gc()方法只是发出一个通知:建议触发GC。

当4M的byte数组真正被回收的时候,虚引用加入引用队列。因此一开始获取队列头的时候队列头为null,休眠100ms之后确保对象被回收,队列头中有虚引用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jimoer

JVM学习记录-对象已死吗

前言 先来回顾一下,在jvm运行时数据区,分为两部分,一个部分是线程共享区,主要包括堆和方法区,另一部是线程私有区分包括本地方法栈,虚拟机栈和程序计数器。在线程...

36360
来自专栏个人分享

JAVA 几种引用类型学习

1、对象的强、软、弱和虚引用     在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(re...

37520
来自专栏向治洪

java的四种引用类型

java的引用分为四个等级:4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 ⑴强引用(StrongReference) 强引用是使用最普遍的引用。...

21750
来自专栏java初学

java — 垃圾回收

434100
来自专栏ppjun专栏

Android十八章:Java硬软弱虚引用,GC回收,内存碎片

又叫java虚拟机栈区,是每一个方法被执行的时候,创建出一个栈帧用来放的成员变量,操作链表,动态链接,方法出口。很多个栈帧又存储在栈区。

18320
来自专栏刘望舒

Java虚拟机(三)垃圾标记算法与Java对象的生命周期

前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的...

20660
来自专栏空帆船w

Java的强引用,软引用,弱引用,虚引用及其使用场景

从 JDK1.2 版本开始,Java 把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引...

40720
来自专栏Java与Android技术栈

Scala学习笔记(四) 类的初步Scala 类相关总结

终于来到面向对象的地方了,虽说函数式编程是 Scala 的特性,让人们觉得 Scala 是更好的 Java。但是在架构层面上一直提倡着:小处用函数式编程,大处用...

12420
来自专栏Lambda

Java 常见内存溢出异常与代码实现

Java 堆 OutOfMemoryError Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间...

22180
来自专栏互联网大杂烩

腾讯面试

时间:2017年3月5号 这次面试和之前的面试差不多,一开始聊项目。聊了项目之后就问基础了,项目的话就不说了。

8720

扫码关注云+社区

领取腾讯云代金券