-XX:+PrintCompilation 不能直接判断自动装箱是否影响 JIT 优化,但可通过对比装箱/非装箱代码的编译日志(如是否编译、内联、再编译痕迹)间接观察其对内联、逃逸分析等的影响。

-XX:+PrintCompilation 本身不会直接告诉你某段代码是否因自动装箱被 JIT 编译优化或阻碍优化,但它能帮你观察 JIT 编译行为的变化——而自动装箱正是影响编译决策(如内联、逃逸分析、标量替换)的关键因素之一。要通过它“观察自动装箱的影响”,核心是:构造可对比的代码对,触发 JIT 编译后,看编译日志中方法是否被编译、编译耗时、是否被内联、是否出现 made not entrant 或 made zombie 等再编译痕迹。
1. 准备可对比的测试代码
写两个逻辑等价但装箱方式不同的方法,例如:
-
有自动装箱版本:
Integer sum = 0; for (int i = 0; i (每次循环都触发 <code>Integer.valueOf()和intValue()) -
无装箱版本:
int sum = 0; for (int i = 0; i
把它们放在独立的、被频繁调用的静态方法中(如 benchBoxed() / benchUnboxed()),并用循环反复调用(≥10000 次),确保触发 C1/C2 编译阈值(默认 C2 是 10000 次调用)。
2. 启动 JVM 并开启详细编译日志
运行时加上这些参数:
立即学习“Java免费学习笔记(深入)”;
-XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintInlining \ -XX:+PrintEscapeAnalysis \ -XX:+PrintOptoAssembly \ -XX:CompileCommand=print,*YourClass.benchBoxed
其中 -XX:+PrintInlining 很关键——它会显示 JIT 是否内联了 Integer.valueOf() 或 intValue();若看到类似 @ 3 java.lang.Integer::intValue (5 bytes) inline (hot),说明装箱相关方法被内联了;若显示 too big 或 not inlineable,则可能因装箱逻辑复杂导致内联失败,进而影响整体优化。
3. 关注日志中的关键信号
在输出中留意以下几类行:
-
编译条目:
123 45 b java.lang.Integer::valueOf (36 bytes)→ 表明valueOf被编译,但不等于被优化掉;如果它频繁出现且未被内联,可能是性能瓶颈 -
内联失败提示:
inline fail: too many bytecodes或inline fail: callee is too large→ 自动装箱常引入额外字节码,导致 JIT 放弃内联,进而无法做标量替换或消除对象分配 -
逃逸分析结果:
java.lang.Integer::valueOf escapes或does not escape→ 若显示escapes,说明 JIT 认为该Integer对象逃逸出方法,无法栈上分配或标量替换,必然产生堆分配 -
再编译标记:
made not entrant或made zombie→ 如果benchBoxed方法被多次重编译,可能说明类型不稳定(如sum在某些路径下是null),JIT 放弃激进优化
4. 配合 JFR 或 -XX:+PrintGC 验证效果
-XX:+PrintCompilation 只告诉你“编译发生了什么”,不直接反映性能差异。建议同时加 -Xlog:gc*:gc.log 或使用 JDK Mission Control 抓取运行时 GC 次数和分配量。你会发现:有自动装箱的版本往往伴随大量 Young GC ——因为 Integer.valueOf(i) 在缓存范围外(如 i ≥ 128)会持续新建对象;而 JIT 即使内联了,也无法消除逃逸对象的分配,除非逃逸分析成功且启用标量替换(-XX:+EliminateAllocations,默认开启)。


















