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

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

相关推荐
风吹落叶32573 分钟前
线程的一些事(2)
java·java-ee
海风极客1 小时前
《Go小技巧&易错点100例》第三十三篇
开发语言·后端·golang
养军博客1 小时前
Spring boot 简单开发接口
java·spring boot·后端
喜欢便码1 小时前
xml与注解的区别
xml·java·开发语言
Rubypyrrha2 小时前
Spring MVC常见注解详解
java·spring·mvc
钢铁男儿2 小时前
Python中的标识、相等性与别名:深入理解对象引用机制
java·网络·python
AllenO.o2 小时前
Redis五种数据结构详解
java·数据结构·数据库·redis·缓存
重生之后端学习2 小时前
day23-集合(泛型&Set&数据结构)
java·开发语言·数据结构·算法
码农飞哥3 小时前
互联网大厂Java面试实战:从Spring Boot到微服务的技术问答与解析
java·数据库·spring boot·安全·微服务·面试·电商
雨落白笙3 小时前
端口转发与跨域处理
java