Java 11应用自动装箱触发Long缓存返回错误值问题求助
Java 11线上Long缓存运行时值异常问题排查
已经排查到的确定信息
- 异常出在原生long自动装箱返回的缓存对象上,问题点位为
Long类内部的LongCache静态数组 - 该缓存仅在类加载阶段通过静态代码块初始化填充-127到128区间的Long对象,无公开可调用的更新接口
- 运行一段时间后出现缓存值错位,重启后恢复正常,不存在持久化存储的异常写入路径
- 当前观测到的错位特征为:连续两个缓存索引位置的存储值出现单值覆盖,和初始化预期值偏移量极小
线上实际遇到过同类问题的常见触发场景
- 绕过访问控制的内存误写
只要拿到类静态字段的内存偏移,完全可以通过Unsafe、反射API直接修改静态数组的元素值,根本不需要类本身开放setter接口。很多涉及内存操作的第三方组件,在并发场景下如果地址计算出现偏差,很容易误写相邻内存段,刚好命中LongCache的存储位置,这类问题在旧版本序列化框架、字节码增强工具、AOP框架的历史bug里出现过很多次。 - JNI代码边界校验缺失导致的越界写
如果应用集成了带内存操作的JNI逻辑,没有严格做数组边界校验,运行时的内存写入会直接覆盖相邻的静态内存区域。当前观测到的连续两个索引位置被同一个值覆盖,完全符合越界写单次赋值的特征。 - JIT优化生成的机器码偏移错误
Java 11的C2编译器在编译优化阶段,如果遇到依赖内存地址计算的代码片段,在特定触发条件下会生成偏移错误的机器码,运行时执行时直接覆盖相邻的堆内静态存储区。 - 堆外内存映射错位
应用使用的直接内存、文件映射内存如果出现内存段映射偏移,会把堆内的LongCache静态存储区当成可写的堆外内存段,写入业务数据时直接覆盖原有缓存值。
快速定位的实操方法
- 启动后增加定时校验逻辑,固定间隔遍历LongCache数组的所有值,标记首次出现值错位的时间点,对齐该时间点的GC日志、JIT编译日志、JNI调用日志、第三方组件操作记录
- 临时增加JVM参数
-XX:-UseCompressedOops重启验证,如果调整后异常不再出现,基本可以确认异常来自内存偏移计算类问题 - 逐一排查应用依赖的涉及Unsafe操作、字节码增强、序列化的第三方组件版本,核对是否存在Java 11环境下的已知内存越界缺陷
内容的提问来源于stack exchange,提问作者henryjin
相关产品推荐
相关产品推荐

