一个场景搞明白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) 就像在资源回收系统里 加了一道"别动,还在用!"的警示线

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

相关推荐
LiuYaoheng4 分钟前
深入理解Java包装类:自动装箱拆箱与缓存池机制
java·缓存
欲儿6 分钟前
RabbitMQ原理及代码示例
java·spring boot·后端·rabbitmq
林 子7 分钟前
Spring Boot自动装配原理(源码详细剖析!)
spring boot·后端
编程轨迹9 分钟前
使用 Spring 和 Redis 创建处理敏感数据的服务
后端
未完结小说41 分钟前
服务注册与发现(nacos)
后端
寒也1 小时前
识别法院PDF文件特定字段并插入数据库【正则表达式+本地化部署】
java·数据库·正则表达式·eclipse·pdf·达梦·ruoyi
AI智能科技用户7946329781 小时前
okcc呼叫中心两个sip对接线路外呼任务怎么设置才能一个任务对应yigesip中继?
人工智能·后端
中国lanwp1 小时前
Spring Boot 版本与对应 JDK 版本兼容性
java·开发语言·spring boot
懒虫虫~1 小时前
Spring源码中关于抽象方法且是个空实现这样设计的思考
java·后端·spring
码银1 小时前
【Java】接口interface学习
java·开发语言·学习