Ja va 中通过 Unsafe 动态读写对象的 private 字段,这事儿说白了就是绕过 JVM 的访问控制,直接对着对象在堆内存里的偏移地址下黑手。属于底层骚操作,只在极少数场景——比如高性能框架、序列化库、JVM 工具开发中才会用到。**业务代码千万别碰**,而且从 JDK 9 开始已经被严格限制,到了 JDK 17+ 默认就不可用了(得额外加 JVM 参数并声明模块权限才能玩)。
Ja va中通过Unsafe动态读写private字段本质是绕过JVM访问控制、直接操作内存偏移,仅限高性能框架等特定场景,不推荐业务使用;JDK9+强限制,需模块权限或JVM参数;推荐改用VarHandle等官方替代方案。

获取 Unsafe 实例(JDK 9+ 需特殊处理)
Unsafe 的 getUnsafe() 方法已经不对外公开了——只有 Bootstrap ClassLoader 加载的类才能直接调。常见的兼容手法有这么几种:
- 暴力反射拿单例(JDK 8 还能凑合,JDK 9+ 得加
--illegal-access=permit或--add-opens) - 用
jdk.internal.misc.Unsafe并配合模块导出(启动参数:--add-modules jdk.unsupported --add-opens ja va.base/jdk.internal.misc=ALL-UNNAMED) - 更稳妥的做法:老老实实用官方替代方案,比如
VarHandle(JDK 9 引入,线程安全、跨平台、JVM 保护妥妥的)
计算字段内存偏移量
Unsafe 不认字段名,只认内存地址偏移。得靠 objectFieldOffset() 拿到目标 private 字段在对象内的字节偏移:
- 字段必须是实例字段或静态字段;但
static final常量会被 JVM 内联,根本没法可靠定位 - 静态字段用
staticFieldOffset(),实例字段用objectFieldOffset(Field) - 注意:字段偏移量依赖 JVM 实现(比如是否开启指针压缩),不同 JVM 参数下结果可能不一样
执行读写操作(以 int 类型 private 字段为例)
拿到 Unsafe 实例和字段偏移后,就能用原子或非原子方法直接读写了:
- 读取:
unsafe.getInt(obj, offset)(obj 不能是 null,offset 是字段在该对象中的偏移) - 写入:
unsafe.putInt(obj, offset, newValue) - 如果是
private static字段,用getInt(null, offset)/putInt(null, offset, v) - 其他类型对应
getLong、getObject、putObject等;如果要保证 volatile 语义,用getAndSetInt这类原子方法
关键风险与替代建议
直接操作内存字段,稍不留神就会翻车:
- JVM 优化(字段重排序、常量折叠、逃逸分析)可能让偏移失效或读到异常值
- GC 移动对象时,
Unsafe操作还按原地址读写,数据直接错乱(不过对堆内对象,JVM 会保证偏移有效,实际不用手动处理) - 模块系统隔离后,反射 + Unsafe 的组合在生产环境大概率直接报错
- 优先用
VarHandle(支持 private 字段、编译器/JVM 优化友好、有明确的内存模型语义),或者MethodHandles.Lookup(JDK 15+ 支持私有字段查找)