Java 性能天花板:JIT 即时编译、分层编译与代码缓存深度调优指南

Java 性能天花板:JIT 即时编译、分层编译与代码缓存深度调优指南 引言很多Java开发者写了多年代码却始终搞不懂这些核心问题为什么本地测试代码运行正常线上压测时性能始终上不去为什么同样的代码JVM运行几分钟后响应速度会提升数倍为什么服务平稳运行很久后会突然出现响应时间飙升、性能暴跌的情况这些问题的核心答案都指向JVM的核心性能引擎——JIT即时编译。JIT是Java能兼顾跨平台特性与原生执行性能的核心支撑也是区分初级Java开发者与资深性能调优专家的关键知识点。一、JIT即时编译的核心本质为什么Java需要JIT1.1 Java的双重执行模型Java是典型的半编译半解释型语言完整的执行流程分为两个核心阶段前端编译javac编译器将.java源码编译为符合JVM规范的.class字节码文件这个阶段仅做语法校验、字节码生成几乎不做任何性能优化仅保留少量常量折叠等基础优化。运行时执行JVM加载字节码后由执行引擎负责执行执行引擎提供了两种核心执行方式解释执行与编译执行JIT。1.2 两种执行方式的核心差异执行方式核心原理优势劣势解释执行解释器逐条将字节码翻译为机器码执行启动速度快无需编译等待无额外内存开销重复执行的代码需反复翻译执行效率低峰值性能差JIT编译执行识别热点代码一次性编译为本地机器码并缓存后续直接执行热点代码执行效率接近原生编译语言峰值性能极高编译需消耗CPU资源有额外的内存开销启动阶段性能差1.3 JVM默认的混合执行模式JDK 17默认开启混合模式-Xmixed将解释器与JIT编译器的优势结合服务启动阶段采用解释执行快速完成启动与初始化降低启动延迟运行过程中持续识别热点代码交给JIT编译器逐步编译优化不断提升执行性能平稳运行阶段核心热点代码全部完成编译优化达到峰值性能1.4 JIT的两大核心编译器JDK 17的JIT包含两个不同定位的编译器配合分层编译机制协同工作64位服务端模式下默认同时启用C1编译器Client Compiler面向客户端场景优化策略保守编译速度快CPU占用低仅做轻量级优化核心目标是降低启动延迟、减少编译开销C2编译器Server Compiler面向服务端场景优化策略激进编译速度慢CPU占用高基于运行时数据做全局深度优化核心目标是实现极致的峰值性能❝注意JDK 8之后64位JVM已废弃-client参数JDK 17中该参数无任何效果默认启用服务端模式与分层编译。二、分层编译JIT的智能调度核心2.1 分层编译的诞生背景在分层编译出现之前JVM只能选择单一编译器要么用C1牺牲峰值性能换启动速度要么用C2牺牲启动速度换峰值性能无法兼顾两者。JDK 7引入分层编译机制JDK 8默认开启JDK 17进一步优化将C1与C2编译器结合通过不同的编译层级实现「启动速度峰值性能」的兼顾同时通过运行时数据智能调度编译流程避免资源浪费。2.2 分层编译的5个层级基于OpenJDK官方规范JDK 17的分层编译分为5个固定层级每个层级有明确的职责与优化策略Tier 0解释执行层代码由解释器执行收集完整的profiling数据方法调用次数、循环执行次数、分支跳转情况、类型信息等为后续编译提供精准的数据支撑Tier 1C1全优化层由C1编译器编译不带任何profiling信息执行完整的C1级优化生成最高质量的C1代码适用于简单无优化空间的方法编译后直接进入最终状态无需再编译到C2Tier 2C1有限profiling层由C1编译器编译仅收集部分关键profiling数据编译速度快于Tier 3适用于中等热度的方法在C2队列积压时快速提升性能Tier 3C1全profiling层由C1编译器编译收集完整的profiling数据编译速度慢于Tier 2是绝大多数热点方法的必经层级为后续C2的深度激进优化提供完整的运行时数据Tier 4C2终极优化层由C2编译器编译基于Tier 3收集的完整profiling数据执行全局的、激进的深度优化生成最高性能的本地机器码是核心热点代码的最终形态2.3 分层编译的核心流转规则JDK 17默认的分层编译流转路径严格遵循「热度优先、资源最优」的原则核心流转逻辑如下方法初始执行时进入Tier 0解释执行收集profiling数据当方法调用次数循环回边次数达到Tier 3阈值默认2000次进入Tier 3由C1编译收集完整profiling数据当方法在Tier 3的执行次数达到Tier 4阈值默认15000次进入Tier 4由C2编译生成终极优化的机器码对于简单方法字节码大小小于-XX:MaxTrivialSize默认6字节直接从Tier 0编译到Tier 1无需profilingC1编译后即达到最优状态当C2编译队列严重积压时热点方法先从Tier 0编译到Tier 2快速提升执行性能待C2队列空闲后再编译到Tier 42.4 分层编译的核心配置参数JDK 17分层编译的核心参数如下非特殊场景不建议修改默认值参数作用JDK 17默认值-XX:TieredCompilation开启分层编译默认开启-XX:TieredStopAtLevelN停止编译的最高层级1仅C14C1C24-XX:Tier3InvokeThreshold进入Tier 3的调用次数阈值2000-XX:Tier4InvokeThreshold进入Tier 4的调用次数阈值15000-XX:CICompilerCountJIT编译线程总数C2线程数该值的2/3CPU核心数的1/2最小2最大8❝注意非分层编译模式下的-XX:CompileThreshold参数在分层编译模式下完全不生效请勿混淆。三、代码缓存JIT编译的核心载体3.1 代码缓存的核心本质JIT编译生成的本地机器码会存储在JVM在堆外申请的一块连续Native内存中这块内存就是代码缓存Code Cache。除了JIT编译的代码代码缓存还会存储JVM内部的Native方法桩、解释器的辅助代码等固定内容。代码缓存是JIT运行的核心载体一旦代码缓存耗尽JVM会直接关闭JIT编译器所有代码退回到解释执行服务性能会出现断崖式下跌。3.2 JDK 17的分段式代码缓存架构JDK 9之后引入分段式代码缓存架构解决了传统单一代码缓存的内存碎片、回收效率低、性能下降的问题JDK 17延续并优化了该架构将代码缓存分为三个独立的内存段每个段有独立的内存管理与垃圾回收机制Non-method段存储非方法的固定代码如JVM内部的Native方法桩、解释器辅助代码默认占总大小的5%固定不回收Profiled-code段存储带profiling信息的临时编译代码即Tier 2、Tier 3的C1编译代码默认占总大小的37%生命周期短会被C2编译后的代码替换支持主动回收Non-profiled-code段存储不带profiling信息的长期优化代码即Tier 1的C1代码与Tier 4的C2代码默认占总大小的58%生命周期长回收频率极低3.3 代码缓存的核心配置参数JDK 17 64位服务端模式下代码缓存的核心参数如下参数作用默认值-XX:InitialCodeCacheSize代码缓存初始大小2MB-XX:ReservedCodeCacheSize代码缓存最大可用大小240MB-XX:CodeCacheExpansionSize代码缓存每次扩容的大小64KB-XX:UseCodeCacheFlushing开启代码缓存刷新回收自动清理无用编译代码默认开启-XX:CodeCacheMinimumFreeSpace代码缓存最小剩余空间低于该值会关闭JIT500KB3.4 代码缓存的监控与风险防控代码缓存耗尽是线上最常见的JIT性能坑当代码缓存剩余空间低于CodeCacheMinimumFreeSpace阈值时JVM会输出CodeCache is full. Compiler has been disabled.警告直接关闭JIT编译器新的热点代码只能解释执行服务响应时间会飙升数倍甚至数十倍。3.4.1 代码缓存监控工具类以下工具类基于JMX实现代码缓存的实时监控可集成到项目中package com.jam.demo.jit; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; /** * JIT代码缓存监控工具类 * author ken * date 2026-03-16 */ Slf4j public class CodeCacheMonitor { private static final String CODE_CACHE_POOL_NAME Code Cache; /** * 获取Code Cache的内存使用情况 * return Code Cache内存使用对象无对应MXBean时返回null */ public static MemoryUsage getCodeCacheUsage() { for (MemoryPoolMXBean poolBean : ManagementFactory.getMemoryPoolMXBeans()) { if (CODE_CACHE_POOL_NAME.equals(poolBean.getName())) { return poolBean.getUsage(); } } return null; } /** * 打印Code Cache的使用详情 */ public static void printCodeCacheInfo() { MemoryUsage codeCacheUsage getCodeCacheUsage(); if (ObjectUtils.isEmpty(codeCacheUsage)) { log.warn(未获取到Code Cache的内存池信息); return; } long initSize codeCacheUsage.getInit() / 1024 / 1024; long usedSize codeCacheUsage.getUsed() / 1024 / 1024; long maxSize codeCacheUsage.getMax() / 1024 / 1024; long committedSize codeCacheUsage.getCommitted() / 1024 / 1024; double usageRate (double) usedSize / maxSize * 100; log.info(Code Cache使用详情); log.info(初始大小: {}MB, initSize); log.info(已用大小: {}MB, usedSize); log.info(最大可用大小: {}MB, maxSize); log.info(已提交大小: {}MB, committedSize); log.info(使用率: {:.2f}%, usageRate); log.info(); if (usageRate 90) { log.error(Code Cache使用率超过90%存在JIT关闭风险请及时调整ReservedCodeCacheSize参数); } else if (usageRate 80) { log.warn(Code Cache使用率超过80%请注意监控); } } public static void main(String[] args) { printCodeCacheInfo(); } }3.4.2 命令行监控方式JDK自带的jcmd工具是线上代码缓存监控的首选无需额外安装核心命令如下查看代码缓存整体使用情况jcmd pid Compiler.codecache查看所有已编译的代码列表jcmd pid Compiler.codelist查看JIT编译队列与积压情况jcmd pid Compiler.queue查看JIT编译器整体统计信息jcmd pid Compiler.stat3.4.3 代码缓存调优最佳实践不盲目调大ReservedCodeCacheSize默认240MB足以应对绝大多数业务场景仅当监控到使用率持续超过80%或出现JIT关闭警告时再调整为512MB~1GB始终保持-XX:UseCodeCacheFlushing开启自动回收无用的编译代码释放内存空间对于短生命周期的CLI工具可设置-XX:TieredStopAtLevel1仅使用C1编译大幅减少代码缓存占用加快启动速度减少运行时动态生成的类如大量反射、动态代理、热部署这类类会生成大量临时方法占用代码缓存空间四、JIT的核心优化技术从原理到可复现实例JIT的优化不是盲目的而是基于运行时收集的profiling数据执行「基于假设的激进优化」如果运行时假设成立优化后的代码性能极高如果假设不成立会触发去优化Deoptimization退回到解释执行重新收集数据后再次编译。以下是JIT最核心的优化技术全部基于JDK 17默认配置配合可直接运行的基准测试实例直观展示优化效果。4.1 方法内联JIT最重要的优化没有之一4.1.1 内联的核心原理方法内联是JIT最核心的优化是其他所有优化的基础。所谓内联就是将被调用方法的代码直接复制到调用方的方法体中消除方法调用的开销栈帧创建、参数传递、方法返回等更重要的是内联后JIT可以对更大的代码块做更深度的优化比如常量传播、死代码消除等。举个通俗的例子原始代码public int add(int a, int b) { return a b; } public int calculate() { return add(1, 2) add(3, 4); }内联后的代码public int calculate() { return (1 2) (3 4); }最终通过常量折叠直接优化为return 10;完全消除计算开销。4.1.2 方法内联的触发条件JDK 17默认的内联触发规则如下被调用方法的字节码大小小于-XX:MaxInlineSize默认35字节无论是否热点都会被内联热点方法调用次数超过-XX:InlineFrequencyCount默认100次字节码大小小于-XX:FreqInlineSize默认325字节会被内联方法必须是非虚方法private、final、static方法或JIT能确定唯一实现类的虚方法否则无法内联4.1.3 方法内联性能基准测试以下基准测试基于OpenJDK官方的JMH工具实现直观对比强制内联与禁止内联的性能差异package com.jam.demo.jit; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * 方法内联性能基准测试 * author ken * date 2026-03-16 */ BenchmarkMode(Mode.Throughput) OutputTimeUnit(TimeUnit.MILLISECONDS) Warmup(iterations 3, time 1, timeUnit TimeUnit.SECONDS) Measurement(iterations 5, time 1, timeUnit TimeUnit.SECONDS) Fork(value 1, jvmArgsPrepend { --add-exports, java.base/jdk.internal.vm.annotationALL-UNNAMED, -XX:TieredCompilation, -XX:Inline }) State(Scope.Benchmark) public class MethodInlineBenchmark { private int a 10; private int b 20; /** * 强制内联的方法 */ jdk.internal.vm.annotation.ForceInline private int forceInlineAdd(int x, int y) { return x y; } /** * 禁止内联的方法 */ jdk.internal.vm.annotation.DontInline private int dontInlineAdd(int x, int y) { return x y; } /** * 强制内联的基准测试 * return 计算结果 */ Benchmark public int forceInlineTest() { int sum 0; for (int i 0; i 1000; i) { sum forceInlineAdd(a, b); } return sum; } /** * 禁止内联的基准测试 * return 计算结果 */ Benchmark public int dontInlineTest() { int sum 0; for (int i 0; i 1000; i) { sum dontInlineAdd(a, b); } return sum; } public static void main(String[] args) throws RunnerException { Options options new OptionsBuilder() .include(MethodInlineBenchmark.class.getSimpleName()) .build(); new Runner(options).run(); } }测试结果说明强制内联的吞吐量是禁止内联的10~20倍性能差异极其显著。4.1.4 方法内联的最佳实践尽量编写小方法避免超大方法大方法无法被内联会丧失大量优化空间优先使用private、final、static方法避免虚方法的动态分派提升内联成功率不盲目调大MaxInlineSize与FreqInlineSize过大会导致编译时间变长、代码缓存占用激增反而影响整体性能仅在性能瓶颈明确的热点方法上使用ForceInline注解禁止滥用4.2 逃逸分析JIT深度优化的基础4.2.1 逃逸分析的核心原理逃逸分析是JIT在编译时执行的对象作用域分析核心是判断一个对象是否会逃逸出当前方法或当前线程如果对象没有逃逸JIT就可以对这个对象执行激进的深度优化。逃逸分为两个级别逃逸级别越低优化空间越大无逃逸对象仅在当前方法内创建和使用不会传递到任何外部方法方法逃逸对象在当前方法内创建被传递到外部方法如作为返回值、方法参数线程逃逸对象在当前线程内创建被传递到其他线程如赋值给静态变量、全局实例变量JDK 17默认开启逃逸分析参数为-XX:DoEscapeAnalysis非特殊场景禁止关闭。4.2.2 基于逃逸分析的三大核心优化标量替换如果对象无逃逸JIT不会在堆上创建该对象而是将对象的成员变量拆解为一个个不可再分的标量基础数据类型分配到方法栈帧或CPU寄存器中完全不需要堆内存分配也不需要GC回收性能提升巨大。❝重要纠正很多教程声称JVM实现了「栈上分配」实际上JVM并没有真正的栈上分配实现对象栈上分配效果的核心是标量替换这是绝大多数教程都存在的错误认知。同步消除如果对象无逃逸仅在当前线程内访问那么该对象的synchronized同步操作不存在线程竞争JIT会直接消除该同步锁完全避免锁的开销也叫锁削除。分离对象读写如果对象仅在方法内部分支使用JIT会将对象的创建延迟到实际使用的分支避免无用的对象创建开销。4.2.3 逃逸分析性能基准测试以下基准测试直观对比开启与关闭逃逸分析的性能差异可直接在JDK 17环境下运行package com.jam.demo.jit; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * 逃逸分析性能基准测试 * author ken * date 2026-03-16 */ BenchmarkMode(Mode.Throughput) OutputTimeUnit(TimeUnit.MILLISECONDS) Warmup(iterations 3, time 1, timeUnit TimeUnit.SECONDS) Measurement(iterations 5, time 1, timeUnit TimeUnit.SECONDS) Fork(value 1) State(Scope.Benchmark) public class EscapeAnalysisBenchmark { /** * 无逃逸的用户对象 */ static class User { private String name; private int age; public User(String name, int age) { this.name name; this.age age; } public int getAge() { return age; } public String getName() { return name; } } /** * 开启逃逸分析的测试对象无逃逸会被标量替换 * return 年龄总和 */ Benchmark Fork(value 1, jvmArgsPrepend {-XX:DoEscapeAnalysis}) public int escapeAnalysisEnableTest() { int sum 0; for (int i 0; i 1000; i) { User user new User(test, i); sum user.getAge(); } return sum; } /** * 关闭逃逸分析的测试对象必须在堆上分配会触发GC * return 年龄总和 */ Benchmark Fork(value 1, jvmArgsPrepend {-XX:-DoEscapeAnalysis}) public int escapeAnalysisDisableTest() { int sum 0; for (int i 0; i 1000; i) { User user new User(test, i); sum user.getAge(); } return sum; } public static void main(String[] args) throws RunnerException { Options options new OptionsBuilder() .include(EscapeAnalysisBenchmark.class.getSimpleName()) .build(); new Runner(options).run(); } }测试结果说明开启逃逸分析的吞吐量是关闭的50倍以上因为开启后User对象被标量替换无堆分配、无GC开销性能提升极其显著。4.2.4 逃逸分析的最佳实践尽量缩小对象的作用域能在方法内创建的对象不要传递到外部方法避免方法逃逸尽量不将对象赋值给静态变量或全局实例变量避免线程逃逸始终保持逃逸分析开启JDK 17的逃逸分析已经非常成熟优化效果显著4.3 栈上替换OSR循环体的专属优化4.3.1 OSR的核心原理JIT不仅会编译被多次调用的方法还会编译被多次执行的循环体——即使这个方法只被调用一次只要循环体的执行次数达到阈值JIT就会编译该循环体的机器码然后在循环执行过程中直接替换栈上的代码继续执行不需要等待方法执行结束这个机制就是栈上替换On-Stack Replacement, OSR。JDK 17分层模式下循环回边次数达到10000次就会触发OSR编译。OSR解决了单次调用的大循环方法的性能问题是大数据量循环计算场景的核心优化。4.3.2 OSR示例与验证以下示例可直观看到OSR的触发启动时添加-XX:PrintCompilation参数即可查看OSR编译日志package com.jam.demo.jit; import lombok.extern.slf4j.Slf4j; /** * OSR栈上替换示例 * author ken * date 2026-03-16 */ Slf4j public class OSRDemo { /** * 大循环方法仅调用一次触发OSR */ public static void bigLoop() { long sum 0; long startTime System.currentTimeMillis(); // 循环1亿次触发OSR编译 for (int i 0; i 100_000_000; i) { sum i; } long endTime System.currentTimeMillis(); log.info(循环执行结束sum{}耗时{}ms, sum, endTime - startTime); } public static void main(String[] args) { // 方法仅调用一次仍会触发OSR bigLoop(); } }启动后控制台会输出类似以下日志其中的%符号代表这是一次OSR编译118 45 % 3 com.jam.demo.jit.OSRDemo::bigLoop 12 (46 bytes)4.4 其他核心优化技术4.4.1 常量传播与折叠常量传播JIT将运行时确定的常量传递到所有使用该常量的位置常量折叠在编译期直接计算常量表达式的结果无需运行时计算。 示例public int calculate() { int a 10; int b 20; return a * b 5; }JIT优化后直接变为return 205;完全消除运行时计算开销。4.4.2 死代码消除JIT会将永远不会执行的代码、执行结果无任何使用的代码直接删除避免无用的执行开销。 示例public void deadCodeTest() { int a 10; int b 20; int sum a b; // 永远不会执行的代码会被直接消除 if (sum 100) { System.out.println(sum is too big); } // sum无任何使用整个计算逻辑都会被消除 }该方法被JIT编译后方法体内的代码会被全部删除因为无任何副作用。4.4.3 循环优化JIT针对循环提供了多种深度优化核心包括循环不变量外提将循环体中不会变化的代码提到循环外部避免每次循环重复执行循环展开将循环的多次迭代合并为一次减少循环判断与跳转的开销范围检查消除消除数组访问时不必要的下标范围检查提升数组访问性能循环剥离将循环的前几次和最后几次迭代单独处理优化循环体的核心逻辑五、JIT监控与线上问题排查实战5.1 JIT编译日志开启与解读JDK 17推荐使用统一日志框架ULF开启JIT日志替代老旧的PrintCompilation参数核心配置如下输出JIT编译基础信息到控制台-Xlog:jitcompilationinfo输出JIT编译详细信息到文件-Xlog:jitcompilationdebug:filejit.log:utctime,level,tags输出去优化日志-Xlog:jitdeoptimizationinfo:filedeopt.log老旧的-XX:PrintCompilation参数在JDK 17中仍可使用输出简洁的编译日志适合快速排查。编译日志核心字段解读以一行典型的编译日志为例1234 123 4 com.jam.demo.jit.MethodInlineBenchmark::forceInlineTest (28 bytes)1234JVM启动后的毫秒数123编译任务的唯一ID4编译的层级Tier 4C2编译方法名与字节码大小附加标记%OSR编译、!方法包含同步块、s同步方法、#去优化5.2 线上常见JIT问题与解决方案问题1服务启动后前几分钟响应慢之后逐渐变快根因JVM启动时采用解释执行核心热点代码需要逐步编译到Tier 4峰值性能需要预热时间这是Java服务的典型特性。解决方案流量预热服务启动后先放小流量逐步放大给JIT足够的时间完成热点代码编译提前编译使用JDK的AOT编译jaotc或GraalVM原生镜像提前将代码编译为本地机器码实现启动即巅峰调整分层编译阈值降低Tier 3与Tier 4的触发阈值让热点代码更早完成编译问题2服务平稳运行后突然出现性能暴跌、响应时间飙升根因大概率是代码缓存耗尽JIT编译器被关闭新的热点代码只能解释执行性能出现断崖式下跌。排查步骤查看JVM日志是否有CodeCache is full. Compiler has been disabled.警告使用jcmd pid Compiler.codecache查看代码缓存使用率是否接近100%解决方案调大-XX:ReservedCodeCacheSize参数根据业务规模调整为512MB~1GB保持-XX:UseCodeCacheFlushing开启自动回收无用编译代码减少运行时动态生成的类降低代码缓存的无效占用问题3服务性能波动大时快时慢根因大概率是JIT频繁触发去优化DeoptimizationJIT基于profiling的假设不成立编译后的代码失效退回到解释执行导致性能波动。排查步骤开启去优化日志查看是否有频繁的去优化事件分析去优化的原因常见原因包括运行时类加载导致类型假设失效、热点代码频繁抛出异常、分支预测频繁失败解决方案避免运行时动态加载类如热部署、频繁动态代理导致类型假设失效避免在热点代码中频繁抛出异常异常会触发去优化减少热点代码中的复杂分支跳转提升分支预测成功率问题4C2编译队列积压热点代码迟迟不编译性能上不去根因热点方法过多C2编译线程数不足编译队列严重积压导致核心热点方法长时间等待编译一直处于解释执行状态。排查步骤使用jcmd pid Compiler.queue查看编译队列是否有大量等待的编译任务使用jcmd pid Compiler.stat查看C2编译线程的任务处理情况解决方案调大-XX:CICompilerCount参数8核以上的服务器可设置为4~8提升编译线程数优化代码减少非核心冷代码的热点化降低编译任务数量5.3 JIT调优的黄金法则不调优就是最好的调优JDK 17的JIT默认参数已经经过了海量场景的优化适合99%的业务场景盲目调优大概率会适得其反先监控再定位最后调优所有调优必须有数据支撑先通过监控找到明确的性能瓶颈再针对性调优禁止凭感觉调优只优化核心热点代码80%的性能提升来自于20%的热点代码只需要针对核心链路的热点代码优化无需关注冷代码六、易混淆概念与常见误区避坑指南误区javac的前端编译优化越多程序运行越快纠正javac几乎不做任何性能优化Java的核心性能优化全部由JIT在运行时完成javac生成的字节码越简单JIT的优化空间越大性能越好。误区JIT编译的代码越多性能越好纠正JIT编译需要消耗大量CPU资源编译后的代码会占用代码缓存内存冷代码编译只会浪费资源不会提升性能只有热点代码编译才会带来性能收益。误区JVM实现了真正的栈上分配将对象分配到栈上纠正JVM没有真正的栈上分配实现对象栈上分配效果的核心是标量替换将无逃逸的对象拆解为标量分配到栈帧与寄存器中不会在堆上创建对象。误区开启-XX:AggressiveOpts就能提升性能纠正该参数在JDK 12之后已被完全废弃JDK 17中该参数无任何效果开启不会带来任何性能提升反而可能引入未知风险。误区使用-Xcomp强制全部编译执行性能会更好纠正-Xcomp会强制JVM在方法第一次调用时就编译为机器码会导致服务启动时间大幅变长冷代码编译浪费大量CPU与代码缓存资源峰值性能反而远不如默认的混合模式生产环境绝对禁止使用。误区C2编译的代码一定比C1快纠正对于执行次数少的冷方法C1编译的代码性能更好因为C1编译速度快、开销小C2的深度优化需要大量编译开销只有执行次数足够多的热点方法才能体现出C2的性能优势。总结JIT即时编译是Java语言能兼顾跨平台特性与原生执行性能的核心底气分层编译让Java实现了启动速度与峰值性能的兼顾代码缓存是JIT稳定运行的核心载体而基于运行时数据的激进优化技术让Java服务能达到接近C等原生编译语言的性能水平。对于Java开发者而言理解JIT的底层原理不仅能帮助你写出更适合JIT优化的高性能代码还能快速定位并解决线上的JIT相关性能问题让你的Java服务达到极致的性能表现。项目依赖POM文件?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.jam.demo/groupId artifactIdjit-demo/artifactId version1.0.0-SNAPSHOT/version properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding lombok.version1.18.34/lombok.version jmh.version1.37/jmh.version spring-core.version6.1.15/spring-core.version guava.version33.2.0-jre/guava.version fastjson2.version2.0.52/fastjson2.version mybatis-plus.version3.5.7/mybatis-plus.version springdoc.version2.6.0/springdoc.version /properties dependencies dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version${lombok.version}/version scopeprovided/scope /dependency dependency groupIdorg.springframework/groupId artifactIdspring-core/artifactId version${spring-core.version}/version /dependency dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version${guava.version}/version /dependency dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version${fastjson2.version}/version /dependency dependency groupIdorg.openjdk.jmh/groupId artifactIdjmh-core/artifactId version${jmh.version}/version /dependency dependency groupIdorg.openjdk.jmh/groupId artifactIdjmh-generator-annprocess/artifactId version${jmh.version}/version scopeprovided/scope /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.13.0/version configuration source17/source target17/target /configuration /plugin /plugins /build /project