> 文章列表 > Java 对象四种引用类型

Java 对象四种引用类型

Java 对象四种引用类型

文章目录

  • Java 对象四种引用类型
    • 强引用(Strong Reference)
    • 软引用(Soft Reference)
    • 弱引用(Weak Reference)
    • 虚引用(Phantom Reference)

Java 对象四种引用类型

在 Java 的对象世界里面,对象的引用有 4 类之分,分别是:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。

强引用(Strong Reference)

在使用new操作符创建的对象,在对象的生命周期内,GC 是不会回收该对象的,除非主动释放该对象(将引用设置为 null),否则 JVM 宁可抛出 OOM 异常,也不会将具有强引用的对象回收。

如下代码所示,将虚拟机的青年带设置为 10M,老年代设置为 20M,创建两个大对象,大小都为 15 M。

package org.gettingreal.jvm.learning.references;/*** 强引用测试*/
public class StrongReferenceTest {public static void main(String[] args) throws Exception {// 大小定位 15 Mint size = 15 * 1024 * 1024;// 创建第一个对象byte[] firstBigObject = new byte[size];// 创建第二个对象byte[] secondBigObject = new byte[size];}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 StrongReferenceTest
java -XX:+PrintGC -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.StrongReferenceTest# 输出
[GC (Allocation Failure)  16195K->15960K(29696K), 0.0012633 secs]
[GC (Allocation Failure)  15960K->15944K(29696K), 0.0007246 secs]
[Full GC (Allocation Failure)  15944K->15672K(29696K), 0.0033753 secs]
[GC (Allocation Failure)  15672K->15672K(29696K), 0.0005597 secs]
[Full GC (Allocation Failure)  15672K->15659K(29696K), 0.0027816 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat org.gettingreal.jvm.learning.references.StrongReferenceTest.main(StrongReferenceTest.java:16)

可以看出 JVM 执行多次 GC 操作,发现最终无法回收强引用的对象,抛出 OOM 异常。JVM 内部执行的步骤大体如下:

  • 在创建第一个对象时,JVM 尝试在青年代进行分配
  • 此时 JVM 发现青年代内存不够分配,将尝试在老年代进行分配
  • 发现老年代的内存此时够用,将第一个对象分配在老年代
  • 在创建第二个对象时,JVM 还是尝试在青年代进行分配,
  • 此时 JVM 发现青年代内存不够分配,将尝试在老年代进行分配
  • 此时 JVM 发现老年代内存也是不够用,虚拟机发生 GC 操作
  • 发现老年代已分配的内存不能回收(因为第一个对象是强引用),抛出 OOM 异常

软引用(Soft Reference)

软引用表示一个对象有用,但是非必需的,意思是如果一个对象是软引用的,在内存空间充足的情况下,JVM 不会回收该对象。然后在内存空间不足时,JVM 会将该对象回收。

如下代码所示,测试软引用回收的情况,将虚拟机的青年带设置为 10M,老年代设置为 20M,创建两个大对象,大小都为 15 M。

package org.gettingreal.jvm.learning.references;import java.lang.ref.SoftReference;/*** 软引用测试*/
public class SoftReferenceTest {public static void main(String[] args) throws Exception {// 大小定位 15 Mint size = 15 * 1024 * 1024;// 创建第一个对象byte[] firstBigObject = new byte[size];// 将第一个对象加入到软引用对象中SoftReference softReference1 = new SoftReference(firstBigObject);// 需要手动释放掉强引用firstBigObject = null;// 创建第二个对象byte[] secondBigObject = new byte[size];// 将第一个对象加入到软引用对象中SoftReference softReference2 = new SoftReference(secondBigObject);// 需要手动释放掉强引用secondBigObject = null;}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 SoftReferenceTest
java -XX:+PrintGC -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.SoftReferenceTest# 输出
[GC (Allocation Failure)  16196K->15960K(29696K), 0.0006832 secs]
[GC (Allocation Failure)  15960K->15888K(29696K), 0.0006921 secs]
[Full GC (Allocation Failure)  15888K->15672K(29696K), 0.0031219 secs]
[GC (Allocation Failure)  15672K->15672K(29696K), 0.0003450 secs]
[Full GC (Allocation Failure)  15672K->299K(29696K), 0.0022771 secs]

可以观察到并没有发生 OOM 异常,说明 JVM 回收掉第一个大对象。

可以通过ReferenceQueue来观察引用被回收的情况,只要这个引用被回收,就会将引用添加到此队列中。

package org.gettingreal.jvm.learning.references;import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;/*** 软引用队列测试*/
public class SoftReferenceQueueTest {public static void main(String[] args) throws Exception {// 大小定位 15 Mint size = 15 * 1024 * 1024;// 定义队列ReferenceQueue referenceQueue = new ReferenceQueue();try {// 创建第一个对象byte[] firstBigObject = new byte[size];// 将第一个对象加入到软引用对象中SoftReference softReference1 = new SoftReference(firstBigObject, referenceQueue);System.out.println("添加软引用:" + softReference1);// 需要手动释放掉强引用firstBigObject = null;// 创建第二个对象byte[] secondBigObject = new byte[size];// 将第一个对象加入到软引用对象中SoftReference softReference2 = new SoftReference(secondBigObject, referenceQueue);System.out.println("添加软引用:" + softReference2);// 需要手动释放掉强引用secondBigObject = null;} finally {SoftReference softReference = null;while ((softReference = (SoftReference) referenceQueue.poll()) != null) {System.out.println("回收软引用:" + softReference);}}}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 SoftReferenceQueueTest
java -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.SoftReferenceQueueTest# 输出
添加软引用:java.lang.ref.SoftReference@3d4eac69
[GC (Allocation Failure)  16197K->15976K(29696K), 0.0009770 secs]
[GC (Allocation Failure)  15976K->15912K(29696K), 0.0005638 secs]
[Full GC (Allocation Failure)  15912K->15674K(29696K), 0.0039520 secs]
[GC (Allocation Failure)  15674K->15674K(29696K), 0.0004479 secs]
[Full GC (Allocation Failure)  15674K->301K(29696K), 0.0026771 secs]
添加软引用:java.lang.ref.SoftReference@42a57993
回收软引用:java.lang.ref.SoftReference@3d4eac69

可以看出,第一个软引用被回收了。

弱引用(Weak Reference)

类似于软引用,但是比软引用还要弱一些,用来描述非必需的对象,只要发生 GC 操作,弱引用都会被回收。

如下代码所示,测试弱引用回收的情况,将虚拟机的青年带设置为 10M,老年代设置为 20M,创建两个大对象,大小都为 15 M。

package org.gettingreal.jvm.learning.references;import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;/*** 弱引用测试*/
public class WeakReferenceTest {public static void main(String[] args) throws Exception {// 大小定位 15 Mint size = 15 * 1024 * 1024;// 定义队列ReferenceQueue referenceQueue = new ReferenceQueue();try {// 创建第一个对象byte[] firstBigObject = new byte[size];// 将第一个对象加入到弱引用对象中WeakReference weakReference = new WeakReference(firstBigObject, referenceQueue);System.out.println("添加弱引用:" + weakReference);// 需要手动释放掉强引用firstBigObject = null;// 创建第二个对象byte[] secondBigObject = new byte[size];// 将第一个对象加入到弱引用对象中WeakReference weakReference2 = new WeakReference(secondBigObject, referenceQueue);System.out.println("添加弱引用:" + weakReference2);// 需要手动释放掉强引用secondBigObject = null;// 执行一次 gc 操作System.gc();} finally {WeakReference wr = null;while ((wr = (WeakReference) referenceQueue.poll()) != null) {System.out.println("回收弱引用:" + wr);}}}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 WeakReferenceTest
java -XX:+PrintGC -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.WeakReferenceTest# 输出
添加弱引用:java.lang.ref.WeakReference@3d4eac69
[GC (Allocation Failure)  16197K->15960K(29696K), 0.0008933 secs]
[GC (Allocation Failure)  15960K->15928K(29696K), 0.0008108 secs]
[Full GC (Allocation Failure)  15928K->314K(29696K), 0.0029523 secs]
添加弱引用:java.lang.ref.WeakReference@42a57993
[GC (System.gc())  15838K->15706K(29696K), 0.0004367 secs]
[Full GC (System.gc())  15706K->308K(29696K), 0.0023688 secs]
回收弱引用:java.lang.ref.WeakReference@42a57993
回收弱引用:java.lang.ref.WeakReference@3d4eac69

可以发现,只要发生 GC 操作,弱引用都将会被回收。

虚引用(Phantom Reference)

虚引用实际上是为了资源释放的细粒度控制,但是使用虚引用却需要小心,因为虚引用可能会导致 OOM 的发生。虚引用更倾向于实现程序员对内存回收的细粒度控制,当虚引用会被回收时,向引用系统发出通知,此时可以执行内存的释放相关操作。

虚引用不会主动释放其指向的对象的内存区域,所以当内存满时,会导致 OOM 异常的发生。将虚拟机的青年带设置为 10M,老年代设置为 20M,创建两个大对象,大小都为 15 M。如下代码所示:

package org.gettingreal.jvm.learning.references;import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;/*** 虚引用测试*/
public class PhantomReferenceTest {public static void main(String[] args) {int size = 15 * 1024 * 1024;ReferenceQueue referenceQueue = null;try {// 定义队列referenceQueue = new ReferenceQueue();// 将第一个对象加入到软虚用对象中PhantomReference phantomReference1 = new PhantomReference(new byte[size], referenceQueue);System.out.println("添加虚引用,phantomReference1:" + phantomReference1);// 将第一个对象加入到软引用对象中PhantomReference phantomReference2 = new PhantomReference(new byte[size], referenceQueue);System.out.println("添加虚引用,phantomReference2:" + phantomReference2);System.gc();} finally {System.out.println("\\n检查回收虚引用:");PhantomReference pr = null;while ((pr = (PhantomReference) referenceQueue.poll()) != null) {System.out.println("回收虚引用:" + pr);}}}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 PhantomReferenceTest
java -XX:+PrintGC -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.PhantomReferenceTest# 输出
添加虚引用,phantomReference1:java.lang.ref.PhantomReference@3d4eac69
[GC (Allocation Failure)  16197K->15960K(29696K), 0.0006786 secs]
[GC (Allocation Failure)  15960K->15896K(29696K), 0.0007171 secs]
[Full GC (Allocation Failure)  15896K->15674K(29696K), 0.0032693 secs]
[GC (Allocation Failure)  15674K->15674K(29696K), 0.0005009 secs]
[Full GC (Allocation Failure)  15674K->15661K(29696K), 0.0024825 secs]检查回收虚引用:
回收虚引用:java.lang.ref.PhantomReference@3d4eac69
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat org.gettingreal.jvm.learning.references.PhantomReferenceTest.main(PhantomReferenceTest.java:24)

可以看出并没有释放 phantomReference1对应的内存。可以通过手动调用 PhantomReference 实例的 clear() 方法来释放对应的内存。

package org.gettingreal.jvm.learning.references;import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;/*** 虚引用清理测试*/
public class PhantomReferenceCleanTest {public static void main(String[] args) throws Exception {int size = 15 * 1024 * 1024;ReferenceQueue referenceQueue = null;try {// 定义队列referenceQueue = new ReferenceQueue();// 将第一个对象加入到软虚用对象中PhantomReference phantomReference = new PhantomReference(new byte[size], referenceQueue);System.out.println("添加虚引用,phantomReference1:" + phantomReference);System.gc();} finally {System.out.println("\\n检查回收虚引用:");PhantomReference pr = null;while ((pr = (PhantomReference) referenceQueue.poll()) != null) {System.out.println("回收虚引用:" + pr);// 调用 clear() 方法,模拟内存释放的细粒度操作pr.clear();}}try {Thread.sleep(3000);// 将第二个对象加入到软虚用对象中PhantomReference phantomReference2 = new PhantomReference(new byte[size], referenceQueue);System.out.println("添加虚引用,phantomReference2:" + phantomReference2);System.gc();} finally {System.out.println("\\n检查回收虚引用2:");PhantomReference pr = null;while ((pr = (PhantomReference) referenceQueue.poll()) != null) {System.out.println("回收虚引用:" + pr);// 调用 clear() 方法,模拟内存释放的细粒度操作pr.clear();}}}}

将代码编译后,执行如下命令来观察结果。

# 执行编译
mvn package -DskipTests# 运行 PhantomReferenceCleanTest
java -XX:+PrintGC -Xmn10m -Xms30m -Xmx30m -classpath target/jvm-learnging-1.0.0-SNAPSHOT.jar org.gettingreal.jvm.learning.references.PhantomReferenceCleanTest# 输出
添加虚引用,phantomReference1:java.lang.ref.PhantomReference@3d4eac69
[GC (System.gc())  16197K->15992K(29696K), 0.0009614 secs]
[Full GC (System.gc())  15992K->15675K(29696K), 0.0032103 secs]检查回收虚引用:
回收虚引用:java.lang.ref.PhantomReference@3d4eac69
[GC (Allocation Failure)  15839K->15739K(29696K), 0.0012892 secs]
[GC (Allocation Failure)  15739K->15771K(29696K), 0.0007469 secs]
[Full GC (Allocation Failure)  15771K->308K(29696K), 0.0042175 secs]
添加虚引用,phantomReference2:java.lang.ref.PhantomReference@42a57993
[GC (System.gc())  15832K->15700K(29696K), 0.0004901 secs]
[Full GC (System.gc())  15700K->15669K(29696K), 0.0018866 secs]检查回收虚引用2:
回收虚引用:java.lang.ref.PhantomReference@42a57993

可以发现,phantomReference1 在 GC 操作时,加入到了 referenceQueue 队列,在遍历队列时,调用引用的 clean() 方法来执行对象对应内存的释放。再次添加一个对象时,不会出现 OOM 异常。