前言
想象你在一家"自动清理型"自助餐厅吃饭。你刚刚打完饭(创建了一个对象),还没吃完(对象还在用),但你暂时把餐盘放在桌上,起身去拿调料。服务员是个 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?
在某些场景中,对象本身可能没有强引用,但它的部分属性或方法仍在被使用。如果垃圾回收器在此时回收了该对象,可能会导致程序出现不可预知的行为(如空指针异常或数据损坏)。例如:
-
非强引用对象的部分属性被使用
如果一个对象只有弱引用或软引用,但它的某些字段或方法仍在被访问,垃圾回收器可能会错误地回收该对象。
-
异步操作中的对象生命周期问题
在异步操作中,对象可能在没有强引用的情况下被使用,垃圾回收器可能会在操作完成前回收该对象。
为了解决这些问题,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)
就像在资源回收系统里 加了一道"别动,还在用!"的警示线 。
它不会延长对象生命周期,但会确保某个时刻之前,它绝对还"活着"。