Java reachabilityFence为什么放在资源使用后的finally里
来源:17golang原创
时间:2026-11-15 15:09:24 157浏览 收藏
Reference.reachabilityFence(owner) 放在资源使用之后,是为了让包装对象至少保持强可达直到这次同步使用结束;放进 finally,则让正常返回和异常离开都经过这个保护点。它避免的是因包装对象过早不再可达而触发自动清理,并不负责调用 close,也不能阻止其他线程显式关闭资源。
适合讨论的场景是:Java 对象保存一个外部资源句柄,Cleaner 根据包装对象的可达性兜底释放该资源。业务方法把句柄读到局部变量后,后续操作可能只需要这个数字;“方法还没返回”并不自动等于“包装对象仍然需要保持可达”。
Java 官方文档地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ref/Reference.html
明确需要保护的是资源包装对象
JLS 对可达性的讨论允许实现依据后续计算是否仍需要对象进行优化。局部变量在源码作用域内存在,并不表示对象一定会活到右花括号。对于普通纯内存对象,这通常没有可见影响;当可达性关联外部资源清理时,才需要认真处理这项约定。
可达性规范:https://docs.oracle.com/javase/specs/jls/se25/html/jls-12.html#jls-12.6.1
本文把资源包装对象称为 owner,把持有句柄的独立对象称为清理状态。它们的角色不同:清理状态还活着,不代表 owner 仍然强可达。fence 的参数应当是决定自动清理时机的那个 owner。
准备独立清理状态与资源所有权
先明确前提:同一个外部资源只有一个释放所有者;同步读取期间不允许其他线程显式 close;原生读取返回时已经停止使用资源。Cleaner 的清理动作不能反向持有被注册的包装对象,否则会破坏自动清理的可达性条件。
下面用 NativeBlock 展示结构。JNI读取和释放方法是集成点,需要真实原生库实现;示例没有提供资源分配器、JNI实现或运行结果,也不用于证明某次 GC 必然在某个位置发生。
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.util.concurrent.atomic.AtomicLong;
// 结构示例:原生方法需由真实 JNI 库实现,本类不是独立可运行程序
final class NativeBlock implements AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
// 静态清理状态只保存句柄,不反向引用 NativeBlock
private static final class ReleaseState implements Runnable {
private final AtomicLong handle;
ReleaseState(long ownedHandle) {
handle = new AtomicLong(ownedHandle);
}
long requireHandle() {
long value = handle.get();
if (value == 0) {
throw new IllegalStateException("资源已经关闭");
}
return value;
}
@Override
public void run() {
long value = handle.getAndSet(0);
if (value != 0) {
// 桥接层约定释放操作短小且不抛异常
freeNative(value);
}
}
}
private final ReleaseState state;
private final Cleaner.Cleanable cleanable;
// 调用方转交一个有效非零句柄的唯一释放责任
NativeBlock(long ownedHandle) {
if (ownedHandle == 0) {
throw new IllegalArgumentException("句柄不能为零");
}
state = new ReleaseState(ownedHandle);
cleanable = CLEANER.register(this, state);
}
int readByte() {
try {
long handle = state.requireHandle();
// 该调用必须同步完成使用,不能在返回后继续持有资源
return readNative(handle);
} finally {
// 保活的是被 Cleaner 注册的包装对象,不只是句柄状态
Reference.reachabilityFence(this);
}
}
@Override
public void close() {
// 确定性释放仍由调用方负责
cleanable.clean();
}
private static native int readNative(long handle);
private static native void freeNative(long handle);
}
ReleaseState 使用静态嵌套类,避免隐式捕获外层实例;非零句柄交给该状态管理,零作为已关闭标记。AtomicLong 只帮助记录关闭状态,不自动使“检查后再使用”与 close 互斥。这个限制直接决定示例的适用范围。
Cleaner 官方说明:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ref/Cleaner.html

在同步调用后的 finally 放置 fence
readByte 的 try 中完成资源使用,finally 中对 this 调用 fence。这样保活的是 Cleaner 注册时关联的 NativeBlock,而不是局部 long 句柄。如果读取抛出异常,finally 仍按 Java 的异常处理规则执行。
抽去业务细节后,所需结构如下。它是放置位置的片段,operation 与 owner 都要由实际类提供。
// 仅展示方法内的放置结构;operation 必须在返回前结束资源使用
try {
operation();
} finally {
// owner 是决定自动清理何时可以发生的包装对象
Reference.reachabilityFence(owner);
}
不要把 fence 当成一次“刷新GC状态”的动作,也不要认为调用后对象立刻被回收。Reference 文档明确说明该方法本身不启动垃圾回收、引用处理或终结操作;它提供的是可达性保证。
方法说明:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ref/Reference.html#reachabilityFence(java.lang.Object)
理解异常路径与放错位置的差别
| 写法 | 保护边界 | 问题 |
|---|---|---|
| 先 fence 再使用资源 | 保证只到前面的保护点 | 后续使用没有因此获得保活保证 |
| 使用后普通语句调用 fence | 只覆盖走到该语句的路径 | 此前异常离开时可能跳过 |
| 使用后的 finally 中调用 | 覆盖正常与异常离开路径 | 仍需满足同步使用及关闭约定 |
| 对清理状态而非 owner 调用 | 保活了另一个对象 | 未必保护 Cleaner 注册对象 |
finally 是为了覆盖控制路径;准确的保护对象与覆盖范围同样重要。若方法在另一层已经保证 owner 始终强可达,则不必机械地为每个普通Java方法添加 fence。

另外处理 close 并发与异步资源使用
最容易过度推断的是“有了fence,资源访问就线程安全”。上面的 requireHandle 可能先读到非零值,另一线程随后执行 close,再由读取线程调用原生方法。对象此时完全可以仍然强可达,因此 fence 无法排除这种显式关闭竞态。
如果业务允许并发 close,需要另外设计互斥、读写锁或资源租约,让操作从获取使用权到释放使用权期间都受到保护。仅把句柄换成原子变量不能完成这件事。
异步任务也不能照搬同步方法的边界。原生接口若在返回后继续使用句柄,方法出口的 fence 结束得太早;应让 owner 或明确的租约覆盖任务真正结束、取消和失败清理的路径。任务提交成功不等于资源使用完成。
安排阅读检查与清理责任
把这类代码接入项目时,先确认四件事:fence的参数是否是被清理的包装对象;所有同步资源使用是否都位于保护点之前;异常路径是否经过finally;显式关闭是否由另一套生命周期协议控制。本文提供的是接入时的阅读检查项,没有执行代码或GC复现。
正常业务仍应通过 AutoCloseable 的确定性关闭方式释放资源,Cleaner只承担兜底角色。资源分配失败、注册失败和原生释放失败的处理应按桥接库约定补齐,不能把此处的结构示例当成完整生产级原生资源管理库。
是否可以用 System.gc() 检查 fence 是否生效?单次强制GC请求和日志先后不能证明所有优化场景,不能把偶然未复现当成生命周期正确性依据。
fence 是否可以替代 try-with-resources?不可以。前者处理可达性保证,后者表达离开作用域时关闭资源的责任。
只保存句柄到局部变量够不够?句柄值仍在使用,不一定意味着 owner 仍被需要。需要保护的是控制清理时机的对象,而不是只保留一个数字。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
275 收藏
-
491 收藏
-
150 收藏
-
205 收藏
-
178 收藏
-
文章 · java教程 | 28分钟前 | Java · 集合 · Stream · java Collector List.copyOf Collectors.toUnmodifiableList Stream.toList252 收藏
-
423 收藏
-
223 收藏
-
223 收藏
-
240 收藏
-
180 收藏
-
343 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习