排查 arraycopy 触发的 jit 逆优化需先排除 gc、死循环等常见原因;若定位到含 arraycopy 的高频代码,且 jstack 显示 runnable 与 in_native 高频切换、jstat 显示 compiled 数下降与 invalidated 数激增,并在 jit 日志中发现大量 reason=class_check 或 reason=unstable_if,即可确认为类型假设失效导致的逆优化。

排查 arraycopy 触发的 JIT 逆优化导致 CPU 飙高,关键在于识别“本应被高效内联或向量化、却因逆优化退回到解释执行或低效编译版本”的异常行为。这类问题不表现为典型死循环或锁竞争,而是在特定数据规模、对象形态或调用上下文下,JIT 编译器主动去优化(deoptimization),引发频繁栈替换、解释执行回退和反复重编译,最终推高 CPU。
确认是否为 JIT 逆优化主导
先排除 GC、线程阻塞、业务死循环等常见原因。若已定位到某段含 System.arraycopy 或 Arrays.copyOf 的代码频繁执行,且线程堆栈中反复出现:
-
java.lang.System.arraycopy(Native Method)后紧接Interpreter Entry或SharedRuntime::deopt_blob类似字样(需开启 JVM 调试日志) - jstack 中同一方法在不同 dump 里状态在
RUNNABLE和IN_NATIVE间高频切换,但无明显 I/O 或锁等待 - jstat 显示
Compiled方法数持续下降,Invalidated数量激增(jstat -compiler <pid></pid>)
开启 JIT 相关诊断日志
重启应用时添加以下 JVM 参数(生产环境慎用,建议灰度或短时开启):
-
-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=jit.log:生成 XML 格式 JIT 编译日志 -
-XX:+PrintDeoptimizationDetails -XX:+TraceDeoptimization:打印每次逆优化的触发原因、方法、bci 及现场信息 -
-XX:+PrintAssembly(需 hsdis):查看 arraycopy 调用点是否被向量化/内联,对比优化前后汇编
重点关注日志中 reason=class_check、reason=unstable_if、reason=trap 等字段——若大量逆优化发生在 arraycopy 所在方法的分支判断处(如数组类型检查、长度校验失败),说明运行时类型假设被打破,触发退优化。
复现并分析 arraycopy 上下文特征
JIT 对 arraycopy 的优化高度依赖运行时类型与形状稳定性。常见诱因包括:
-
泛型擦除后实际数组类型不一致:如方法签名
<t> T[] copy(T[] src)</t>,但调用时混用String[]、Integer[]、Object[],导致 C2 无法稳定推测元素类型,放弃向量化 - 数组长度极小或极大波动:JIT 对不同长度会选用不同策略(如 memcpy / loop unrolling / vectorized copy)。若长度在临界值(如 64/256 字节)附近抖动,可能反复触发编译策略切换
-
arraycopy 前存在未稳定的类型检查:如
if (obj instanceof SomeType[]) { arraycopy(...) },但SomeType在运行时有多个子类数组实例,导致分支预测失效、逆优化
用 jcmd <pid> VM.native_memory summary</pid> 辅助确认是否伴随大量 CodeCache 压力(逆优化+重编译会快速耗尽 CodeCache)。
验证与修复方向
临时验证可加参数限制 JIT 行为:
-
-XX:TieredStopAtLevel=1:强制仅使用 C1 编译,避开 C2 逆优化路径(性能损失大,仅用于对比) -
-XX:-UseSuperWord:禁用向量化,看 CPU 是否回落(若回落,说明原向量化逻辑因逆优化反而更差)
根治建议:
- 将 arraycopy 封装为专用工具方法,输入类型尽量具体(避免泛型擦除),并增加
@HotSpotIntrinsicCandidate注解提示(JDK 17+) - 对长度敏感场景,按区间分治:如
length - 用 JMH 写微基准测试,固定数组类型与长度,对比不同 JVM 参数下的吞吐与 GC pause,确认逆优化是否复现










