String不可变性本身安全,但评审需聚焦开发者误用其错觉引发的隐患:敏感信息未擦除、substring内存泄漏、共享引用导致语义混淆、哈希缓存逻辑错配。

关注敏感字符串被直接传递或日志化
不可变 ≠ 安全存储。String 一旦创建就无法擦除,导致密码、token、密钥等在堆内存中长期驻留,GC 也无法及时回收(尤其在常量池中)。
- 检查是否用
String接收或返回密码、API key、JWT payload 等——应强制改用char[]或byte[],并在使用后立即Arrays.fill(arr, '\0')清零 - 排查日志语句:如
log.info("Token: {}", token)—— 即使 token 是不可变的,它仍会完整留在日志文件和内存中;应脱敏为"Token: ***"或使用日志掩码规则 - 注意 Spring Boot 配置类中
@Value("${db.password}")绑定到String字段,属于高风险模式,建议配合@ConfigurationProperties+ 自定义类型封装
警惕“看似修改实则泄露引用”的链式调用
String 方法如 substring(JDK 7u6 之前)、replace、toLowerCase 虽返回新对象,但旧对象若持有大量原始数据(如从大文件读取的长字符串),可能因内部 value 数组共享而造成内存泄漏。
- 检查是否对超长字符串反复调用
substring(0, N)后丢弃原引用——JDK 7u6 后已修复,但仍建议显式构造新字符串:new String(s.substring(0, N))消除底层数组依赖 - 避免将大文本解析结果(如整个 HTTP 响体)直接赋值给局部
String变量后仅提取其中一小段,再让该变量长时间存活
检查跨线程/跨组件共享字符串是否隐含状态耦合
不可变性让 String 天然适合共享,但若多个模块“约定共用某个字符串实例”来传递上下文(如 traceId、tenantId),却未明确生命周期边界,反而容易掩盖竞态或污染。
- 查找类似
public static final String DEFAULT_TENANT = "default"被多处直接修改(通过反射或 Unsafe)的痕迹——虽然非法,但真实系统中已有工具链(如某些 APM 插件)会这么做,评审需结合字节码扫描报告交叉验证 - 审查过滤器、拦截器、AOP 切面中是否对传入的
String path或String authHeader执行了.trim()、.replace()等操作并重新 set 回 request attribute —— 这不改变原对象,但若下游误以为“同一引用=同一语义”,可能跳过校验 - 确认注解属性(如
@RequestMapping(value = "/api/v1"))是否被动态代理或 ASM 修改过字节码——这类篡改虽罕见,但在热部署、低代码平台中已成攻击面
验证哈希相关逻辑是否假设了 String 内容恒定
HashMap / HashSet 的 key 若为 String,其不可变性保证了哈希值稳定。但如果 key 是由用户输入拼接生成,且后续又用该 key 做多次查询,需确认无中间环节意外复用或缓存了错误引用。
立即学习“Java免费学习笔记(深入)”;
- 检查缓存 key 构造:如
cache.get("user:" + userId + ":profile")—— 正常;但若写成String key = "user:" + userId; key += ":profile"; cache.get(key),虽无害,却暴露了对字符串临时对象生命周期的模糊认知 - 识别自定义缓存封装类中是否缓存了
String.hashCode()结果并用于快速比对——这是合理优化;但若缓存的是System.identityHashCode()并用于判断“是否同一对象”,则与不可变性无关,属设计混淆


















