Java中 怎么通过 Unsafe 动态读写对象的 private 内存字段

作者:袖梨 2026-07-27
Java中通过Unsafe动态读写private字段本质是绕过JVM访问控制、直接操作内存偏移,仅限高性能框架等特定场景,不推荐业务使用;JDK9+强限制,需模块权限或JVM参数;推荐改用VarHandle等官方替代方案。

Java 中通过 Unsafe 动态读写对象的 private 字段,本质是绕过 JVM 的访问控制机制,直接操作对象在堆内存中的偏移地址。这属于底层黑盒操作,仅限于特定场景(如高性能框架、序列化库、JVM 工具开发),**不推荐在业务代码中使用**,且自 JDK 9 起被强限制,JDK 17+ 默认不可用(需额外 JVM 参数并声明模块权限)。

获取 Unsafe 实例(JDK 9+ 需特殊处理)

Unsafe 不再提供公开的 getUnsafe() 方法供普通类调用(仅 BootClassLoader 加载的类可获取)。常见兼容方式:

  • 通过反射暴力获取单例(JDK 8 可行,JDK 9+ 需 --illegal-access=permit 或 --add-opens)
  • 使用 jdk.internal.misc.Unsafe 并通过模块导出(需 JVM 启动参数:--add-modules jdk.unsupported --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
  • 更稳妥的做法:改用官方支持的替代方案(如 VarHandle,JDK 9 引入,线程安全、跨平台、受 JVM 保护)

计算字段内存偏移量

Unsafe 不识别字段名,只认内存地址偏移。必须通过 objectFieldOffset() 获取目标 private 字段在对象内的字节偏移:

  • 字段必须是静态或实例字段;static final 常量会被内联,无法可靠定位
  • 使用 staticFieldOffset() 获取静态字段偏移;objectFieldOffset(Field) 获取实例字段偏移
  • 注意:字段偏移依赖 JVM 实现(如是否开启指针压缩),不同 JVM 参数下可能变化

执行读写操作(以 int 类型 private 字段为例)

拿到 Unsafe 实例和字段偏移后,即可用原子/非原子方法读写:

立即学习“Java免费学习笔记(深入)”;

  • 读取:unsafe.getInt(obj, offset)(obj 非 null,offset 是该字段在 obj 中的偏移)
  • 写入:unsafe.putInt(obj, offset, newValue)
  • private static 字段,用 getInt(null, offset) / putInt(null, offset, v)
  • 其他类型对应 getLonggetObjectputObject 等;volatile 语义用 getAndSetInt 等原子方法

关键风险与替代建议

直接操作内存字段极易引发不可预测行为:

  • JVM 优化(如字段重排序、常量折叠、逃逸分析)可能导致偏移失效或值异常
  • GC 移动对象时,Unsafe 操作仍按原地址读写,造成数据错乱(但对堆内对象,JVM 保证偏移有效,实际无需手动处理)
  • 模块系统隔离后,反射 + Unsafe 组合在生产环境大概率失败
  • 优先使用 VarHandle(支持 private 字段、编译器/JVM 优化友好、有明确内存模型语义)或 MethodHandles.Lookup(JDK 15+ 支持私有字段查找)

相关文章

精彩推荐