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 M
        int 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 space
        at 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 M
        int 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 M
        int 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 M
        int 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 space
        at 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 异常。

你可能感兴趣的:(Java,java,jvm,开发语言)