一个场景搞明白Reachability Fence,它就像一道“结账前别走”的红外感应门

前言

想象你在一家"自动清理型"自助餐厅吃饭。你刚刚打完饭(创建了一个对象),还没吃完(对象还在用),但你暂时把餐盘放在桌上,起身去拿调料。服务员是个 AI 清洁机器人,只要发现哪张桌子没人、餐盘没人动,就立马收走洗碗(垃圾回收)。

这时候问题来了:你其实还没吃完啊!但因为你暂时"没有接触"(没有强引用),AI 误以为你走了,直接把盘子刷干净送回厨房,尴尬地导致你回来一看------我的饭呢?

这就是 Java 中的提前回收现象:对象明明还要用,GC 却因为"表面没人用"给你回收了。

于是 Java 9 引入了 reachabilityFence(),这就像是在桌边装了一道 红外感应门

"只要你没通过这道门,我就认为你还在用那盘饭,绝对不收。"

你在吃完饭前走过这个感应门(调用 reachabilityFence(obj)),系统就会 确保你在那之前,对象还"活着",不会被提早洗掉。

Reachability Fence(可达性栅栏) 是 Java 9 引入的一种机制,用于解决某些情况下对象在没有强引用时被提前回收的问题。它的作用是确保在某个代码块执行期间,对象不会被垃圾回收器回收,即使它没有强引用。

我遇到的问题:对象还没用完就被 GC 了?

我们在进行异步内存处理时,创建了一个 Resource 对象,它内部封装了一个 1MB 的缓冲区 buffer。任务被提交到线程池后,在子线程中访问了 buffer,理论上这段内存应该一直有效。

结果却发现,有时候还没等异步任务执行完,Resource 对象就被回收了,还输出了 finalize() 的日志。更离谱的是,程序甚至抛出了 NullPointerException,因为对象已经"死"了。

示例代码

java 复制代码
import java.lang.ref.Reference;
import java.util.concurrent.CompletableFuture;

public class ReachabilityFenceExample {

    static class Resource {
        private final byte[] buffer = new byte[1024 * 1024]; // 1 MB 缓冲区

        public byte[] getBuffer() {
            return buffer;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("Resource 被回收");
            super.finalize();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 创建 Resource 对象
        Resource resource = new Resource();

        // 异步操作:访问 Resource 的缓冲区
        CompletableFuture.runAsync(() -> {
            // 在异步操作中访问 Resource 的缓冲区
            byte[] buffer = resource.getBuffer();
            System.out.println("缓冲区大小: " + buffer.length);

            // 模拟耗时操作
            try {
                Thread.sleep(1000); // 模拟 1 秒的耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 再次访问 Resource 的缓冲区
            System.out.println("再次访问缓冲区大小: " + resource.getBuffer().length);

            // 使用 Reachability Fence 确保 Resource 对象在异步操作中不被回收
            Reference.reachabilityFence(resource);
        });

        // 解除 Resource 的强引用
        resource = null;

        // 触发垃圾回收
        System.gc();

        // 等待异步操作完成
        Thread.sleep(2000);
    }
}

运行结果

没有 Reachability Fence 的情况

注释掉 Reference.reachabilityFence(resource),可能会输出:

txt 复制代码
缓冲区大小: 1048576
Resource 被回收
再次访问缓冲区大小: 1048576

或者(如果垃圾回收发生在第二次访问之前):

txt 复制代码
缓冲区大小: 1048576
Resource 被回收
Exception in thread "ForkJoinPool.commonPool-worker-1" java.lang.NullPointerException

使用 Reachability Fence 的情况

启用 Reference.reachabilityFence(resource),输出:

txt 复制代码
缓冲区大小: 1048576
再次访问缓冲区大小: 1048576
Resource 被回收

当启用 Reference.reachabilityFence(resource) 时,代码的行为如下:

  • resource 对象在 main 方法中被设置为 null,失去了强引用。
  • 由于 Reachability Fence 的存在,垃圾回收器不会在异步操作执行期间回收 resource 对象。
  • finalize 方法不会在异步操作完成之前被调用。
  • 异步操作完成后,resource 对象可能会被回收,finalize 方法会被调用,输出 Resource 被回收

现在我们对代码进行一些调整,如下所示:

java 复制代码
import java.lang.ref.Reference;
import java.util.concurrent.CompletableFuture;

public class ReachabilityFenceExample {

    static class Resource {
        private final byte[] buffer = new byte[1024 * 1024]; // 1 MB 缓冲区

        public byte[] getBuffer() {
            return buffer;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("Resource 被回收");
            super.finalize();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 创建 Resource 对象
        Resource resource = new Resource();

        // 异步操作:访问 Resource 的缓冲区
        CompletableFuture.runAsync(() -> {
            // 在异步操作中访问 Resource 的缓冲区
            byte[] buffer = resource.getBuffer();
            System.out.println("缓冲区大小: " + buffer.length);

            // 使用 Reachability Fence 确保 Resource 对象在异步操作中不被回收
            Reference.reachabilityFence(resource);
        });

        // 解除 Resource 的强引用
        resource = null;

        // 触发垃圾回收
        System.gc();

        // 等待异步操作完成
        Thread.sleep(1000);
    }
}

运行结果

如果注释掉 Reference.reachabilityFence(resource),可能会输出:

txt 复制代码
Resource 被回收
缓冲区大小: 1048576

启用 Reference.reachabilityFence(resource) 后,输出:

txt 复制代码
缓冲区大小: 1048576
Resource 被回收

你观察到了吗?无论是否使用 Reachability Fence,缓冲区的大小都被正确输出了。这是否意味着 Reachability Fence 对代码没有影响?这里为什么不会出现问题呢??


总结

为什么需要 Reachability Fence?

在某些场景中,对象本身可能没有强引用,但它的部分属性或方法仍在被使用。如果垃圾回收器在此时回收了该对象,可能会导致程序出现不可预知的行为(如空指针异常或数据损坏)。例如:

  1. 非强引用对象的部分属性被使用

    如果一个对象只有弱引用或软引用,但它的某些字段或方法仍在被访问,垃圾回收器可能会错误地回收该对象。

  2. 异步操作中的对象生命周期问题

    在异步操作中,对象可能在没有强引用的情况下被使用,垃圾回收器可能会在操作完成前回收该对象。

为了解决这些问题,Java 引入了 Reachability Fence 机制,允许开发者显式地通知 JVM:在某个代码块执行期间,对象仍然是可达的,不应被回收

这种机制通常出现在一些底层优化、Native 内存管理、finalize/cleaner 等场景,特别是在你用完对象,但 JVM 看起来觉得你"已经不用了"的那些模糊时刻。

比如:

  • 调用 JNI 后释放 native 资源前;
  • 对象还在工作,但局部变量/引用早就不在栈上了;
  • Cleaner 线程突然"比你还快"处理了清理任务......

Reachability Fence 的实现

Java 9 在 java.lang.ref.Reference 类中引入了 reachabilityFence 方法。它的作用是确保传入的对象在方法调用点仍然是强可达的。

方法签名:

java 复制代码
public static void reachabilityFence(Object obj)

reachabilityFence(obj) 就像在资源回收系统里 加了一道"别动,还在用!"的警示线

它不会延长对象生命周期,但会确保某个时刻之前,它绝对还"活着"

相关推荐
无限大62 小时前
只出现一次的数字:从暴力美学到位运算神技的进化之路
后端·面试
宇寒风暖2 小时前
Flask 框架全面详解
笔记·后端·python·学习·flask·知识
seabirdssss2 小时前
错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException
java·开发语言
你的人类朋友2 小时前
❤️‍🔥为了省内存选择sqlite,代价是什么
数据库·后端·sqlite
还是鼠鼠2 小时前
tlias智能学习辅助系统--SpringAOP-进阶-通知顺序
java·后端·mysql·spring·mybatis·springboot
Pitayafruit2 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
spring boot·后端·ai编程
君莫笑几人回3 小时前
关于记录一下“bug”,在做图片上传的时候出现的小问题
java·开发语言·spring boot
技术不支持3 小时前
Qt Creator 11.0.3 语法高亮bug问题
java·服务器·数据库·qt·bug
用户21411832636024 小时前
零成本搭建 AI 应用!Hugging Face 免费 CPU 资源实战指南
后端
程序员曦曦4 小时前
15:00开始面试,15:06就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展