Java中String的不可变性是逻辑保障而非JVM强制,反射可修改value字段导致常量池污染、hashCode缓存失效、线程安全破坏及安全机制失灵。

Java 中 String 的“不可变性”是一种逻辑保障,不是 JVM 层面的强制约束。反射能绕过访问控制,直接修改其底层存储数组(JDK8 是 char[],JDK9+ 是 byte[]),从而静默改变字符串内容——但这种操作会引发一连串严重副作用。
反射修改 String 的核心步骤
关键在于获取并篡改私有字段 value:
- 用
String.class.getDeclaredField("value")获取字段对象 - 调用
setAccessible(true)突破 private 访问限制 - 通过
field.get(str)拿到内部数组引用(char[]或byte[]) - 直接修改数组元素,例如
array[0] = 'X',不创建新对象,也不改变变量引用
最危险后果:常量池污染
字面量字符串(如 "hello")默认进入运行时常量池,多个相同字面量共享同一对象:
-
String a = "hello"; String b = "hello";→a == b为true - 若反射修改
a的value数组,b读取时也会得到被篡改后的内容 - 所有指向该常量池项的 String 变量,内容同步“变脏”,排查极其困难
hashCode 缓存失效导致哈希契约破裂
String 内部缓存了 hash 字段(初始为 0),首次调用 hashCode() 后才计算并赋值:
立即学习“Java免费学习笔记(深入)”;
- 反射修改字符数组后,内容变了,但
hash值不会自动重算 - 后续调用仍返回旧值(因
hash != 0,跳过重计算) - 结果是
equals()返回true,但hashCode()不一致 →HashMap/HashSet行为异常
破坏线程安全与安全假设
不可变性是很多基础机制的前提:
- 多线程共享配置字符串(如数据库 URL、SQL 模板)时,开发者默认它不会被其他线程意外修改;反射介入即打破这一前提
- 安全框架依赖 String 存储路径、权限标识、token 等;内容被篡改可能绕过校验或触发非法操作
- 类加载器、安全管理器等底层组件也隐式信任 String 的稳定性


















