Java软引用对象的创建以及对象回收

举例说明如何创建软引用对象。

在 Java 中,软引用是通过 java.lang.ref.SoftReference 类来实现的。

示例代码:

java 复制代码
import java.lang.ref.SoftReference;

public class SoftReferenceExample {

    public static void main(String[] args) {
        // 1. 创建一个普通对象
        MyLargeObject strongRefObject = new MyLargeObject("Initial Data");
        System.out.println("原始对象 (强引用) 创建成功: " + strongRefObject.getData());

        // 2. 创建一个软引用指向这个对象
        // SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);
        // 在创建软引用后,可以将强引用置为 null,这样 MyLargeObject 实例就只剩下软引用了
        SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);
        strongRefObject = null; // 将强引用置为 null

        System.out.println("软引用创建成功。");

        // 3. 尝试通过软引用获取对象
        MyLargeObject retrievedObject = softRef.get();
        if (retrievedObject != null) {
            System.out.println("通过软引用获取到对象: " + retrievedObject.getData());
        } else {
            System.out.println("通过软引用获取对象失败,对象可能已被回收。");
        }

        // 模拟内存紧张的情况,强制进行垃圾回收
        // 在实际应用中,JVM 会根据内存情况自动决定何时回收软引用对象
        // 这里只是为了演示效果,强制执行 GC,但不能保证一定回收
        System.out.println("\n尝试进行垃圾回收...");
        System.gc(); // 提示 JVM 进行垃圾回收
        try {
            Thread.sleep(100); // 等待一小段时间,让GC有机会执行
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }


        // 4. 再次尝试通过软引用获取对象
        MyLargeObject retrievedObjectAfterGC = softRef.get();
        if (retrievedObjectAfterGC != null) {
            System.out.println("GC 后通过软引用获取到对象: " + retrievedObjectAfterGC.getData());
        } else {
            System.out.println("GC 后通过软引用获取对象失败,对象已被回收。");
        }

        // 进一步模拟内存紧张,例如创建大量对象
        System.out.println("\n模拟内存紧张,创建大量对象...");
        // 尝试创建大量对象,耗尽内存,迫使JVM回收软引用对象
        // 注意:这可能导致 OutOfMemoryError,具体取决于JVM的配置和可用内存
        java.util.List<byte[]> memoryEater = new java.util.ArrayList<>();
        try {
            for (int i = 0; i < 1000; i++) { // 创建1000个1MB的字节数组
                memoryEater.add(new byte[1024 * 1024]); // 1MB
            }
        } catch (OutOfMemoryError e) {
            System.out.println("模拟内存紧张成功,发生 OutOfMemoryError。");
        }


        // 5. 在极度内存紧张后,再次尝试通过软引用获取对象
        MyLargeObject retrievedObjectAfterOOM = softRef.get();
        if (retrievedObjectAfterOOM != null) {
            System.out.println("内存紧张后通过软引用获取到对象: " + retrievedObjectAfterOOM.getData());
        } else {
            System.out.println("内存紧张后通过软引用获取对象失败,对象已被回收。");
        }
    }
}

// 模拟一个较大的对象,以便垃圾回收器更容易注意到它
class MyLargeObject {
    private String data;
    // 假设这个对象内部还有一些其他数据,占用内存
    private byte[] largeArray = new byte[1024 * 10]; // 10KB

    public MyLargeObject(String data) {
        this.data = data;
        System.out.println("MyLargeObject created with data: " + data);
    }

    public String getData() {
        return data;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("MyLargeObject (" + data + ") is being finalized.");
        super.finalize();
    }
}

代码解释:

  1. MyLargeObject strongRefObject = new MyLargeObject("Initial Data");

    • 首先,我们创建了一个 MyLargeObject 的实例,并用一个普通的变量 strongRefObject 强引用它。此时,MyLargeObject 实例是强可达的。
  2. SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);

    • 接着,我们创建了一个 SoftReference 对象,并将 strongRefObject 作为参数传给它的构造函数。这意味着 softRef 现在是一个软引用,指向 MyLargeObject 实例。
    • strongRefObject = null; 这一步非常关键。我们将原来的强引用 strongRefObject 置为 null。此时,MyLargeObject 实例就只剩下 softRef 这一个软引用了。这意味着它现在是"软可达"的。
  3. MyLargeObject retrievedObject = softRef.get();

    • 通过 softRef.get() 方法,我们可以尝试获取软引用指向的实际对象。
    • 如果对象还没有被垃圾回收器回收,get() 方法会返回该对象。
    • 如果对象已经被回收,get() 方法会返回 null
  4. System.gc(); 和模拟内存紧张

    • System.gc() 只是一个建议 JVM 执行垃圾回收的操作,JVM 不一定会立即执行,也不保证会回收软引用对象。
    • 为了更可靠地演示软引用在内存紧张时被回收的特性,我们通常需要模拟创建大量对象来耗尽内存。当 JVM 内存不足时,它会优先回收软引用指向的对象。

运行结果分析:

  • strongRefObject = null; 之后,MyLargeObject 实例就只剩下软引用了。
  • 在内存充足的情况下,softRef.get() 仍然可以获取到 MyLargeObject 实例。
  • 当 JVM 内存紧张(例如,通过创建大量对象来模拟),垃圾回收器会回收 MyLargeObject 实例。此时,再次调用 softRef.get() 就会返回 null
  • finalize() 方法会在对象被垃圾回收前调用,可以观察到其输出,进一步证明对象已被回收。

这个例子展示了软引用如何在内存紧张时自动释放对象,这对于实现缓存机制非常有用,既能提高性能,又能避免 OutOfMemoryError

手动执行 strongRefObject = null; 之后,softRef.get() 获取到的不一定是 null

让我来详细解释一下:

  1. strongRefObject = null; 的作用:

    • 这一行代码的作用是断开 strongRefObject 这个变量对 MyLargeObject 实例的强引用
    • 不会立即触发垃圾回收
    • 它只是使得 MyLargeObject 实例现在只剩下软引用 softRef 指向它 。此时,该 MyLargeObject 实例变为了"软可达"状态。
  2. 软引用对象的回收时机:

    • 软引用指向的对象,在内存充足时不会被回收
    • 只有当 JVM 即将耗尽内存(即内存紧张)时,垃圾回收器才会考虑回收这些软引用对象。
  3. softRef.get() 的行为:

    • softRef.get() 方法会尝试获取软引用指向的实际对象。
    • 如果该对象尚未被垃圾回收器回收 (因为内存还充足,或者垃圾回收器还没来得及回收),那么 get() 方法就会返回该对象实例。
    • 只有当该对象已经被垃圾回收器回收 后,get() 方法才会返回 null

所以,在 strongRefObject = null; 之后,MyLargeObject 实例虽然没有了强引用,但它仍然存在于内存中,并且通过 softRef 仍然是可访问的,直到 JVM 认为内存不足并决定回收它。

strongRefObject = null; 之后,以及甚至在第一次 System.gc() 之后,只要内存还充足,softRef.get() 仍然能够获取到对象。只有当模拟了内存紧张,JVM 真正开始回收软引用对象时,softRef.get() 才会返回 null

相关推荐
qq_417695052 小时前
C++中的中介者模式
开发语言·c++·算法
xiangpanf2 小时前
PHP爬虫框架:Goutte vs Panther
开发语言·c++·vue.js·php
~无忧花开~2 小时前
React元素渲染:核心概念全解析
开发语言·前端·javascript·react.js
开开心心就好2 小时前
免费无广告的礼金记账本,安卓应用
java·前端·ubuntu·edge·pdf·负载均衡·语音识别
像素猎人2 小时前
pair<类型1, 类型2> 变量名的介绍,自用笔记
开发语言·c++·算法
向往着的青绿色2 小时前
完全平方数【Letcode279题解】
开发语言·c++·数学·算法·面试·性能优化·动态规划
无籽西瓜a2 小时前
OSI 七层模型详解及面经
java·网络·后端
marsh02062 小时前
14 openclaw模板引擎使用:高效渲染动态内容
java·前端·spring·ai·编程·技术