本文共 1926 字,大约阅读时间需要 6 分钟。
Java 作为一门解释型语言,凭借其“一次编译,处处运行”的特点,凭借着设计上的巧妙之处,在性能与便捷性之间找到了平衡。然而,这种语言的性能通常不如传统的编译型语言(如 C++)。为了弥补这一不足,Java 引入了即时(Just In Time,JIT)编译器,使得运行时环境能够在程序运行过程中对热点代码进行优化,提升性能。
Java 的执行过程主要分为两个阶段。首先,javac 将源码编译成通用的中间形式——字节码。在此过程中,编译器会进行词法分析、语法分析和语义分析。这个阶段称为前端编译。随后,不需要再次编译字节码,直接交给解释器逐条解释执行。在解释执行过程中,虚拟机(JVM)同时对程序运行的信息进行收集,在这些信息的基础上,编译器会逐步发挥作用,进行后端编译,将字节码优化为机器码,但并非所有代码都会被编译,只有被 JVM 认定为热点代码才会被优化。
JVM 集成两种编译器:Client Compiler(C1) 和 Server Compiler(C2)。两者在性能和启动速度之间寻求平衡。
C1 编译器的目标是为快速启动和局部优化做准备。其主要功能包括:
C2 编译器主要关注全局优化,性能优于 C1。其默认的全局优化手段包括:
Java 7 以 Slaurar 展开的分层编译概念结合了 C1 和 C2 的优势。通过对程序运行状态的分层,JVM 可以在保证性能的同时,优化启动速度。分为五个层次:
即时编译的触发条件主要基于方法调用次数和循环回边次数。当调用次数或回边次数超过阈值时,JVM 会触发即时编译。默认阈值由参数 -XX: CompileThreshold 控制。
即时编译器的优化主要包括中间表达形式优化、方法内联以及全局优化。特别是在 C2 编译器中,采用 Ideal Graph 进行全局优化,使得编译效率显著提升。
现代编译器普遍使用静态单赋值(SSA)中间表达形式。SSA IR 在程序优化中具有显著优势,能够简化死代码删除、全局值编号等优化操作。
方法内联是 JIT 的重要优化手段。通过将目标方法的代码与调用者代码合并,减少方法调用开销,显著提升性能。
C2 编译器通过 Ideal Graph 构建程序依赖关系,收集程序运行信息,进行全局优化。此外,结合 Global Value Numbering(GVN)消除公共子表达式,将优化效果最大化。
通过 JITwatch 等工具分析编译日志,了解 JIT 的编译状态,发现热点代码和潜在优化点。
Graal 编译器自 JDK 9 起开始支持,能够显著提升性能。需注意其与 G1垃圾回收器的兼容性,并在启动添加 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 参数。
JVM 的即时编译机制为 Java 提供了性能优化的手段,通过 C1 和 C2 编译器的结合与分层编译,尽可能在性能与启动效率之间取得平衡. Graal 编译器的引入为 Java 的性能提升开辟了新方向,但仍需注意其在启动阶段的性能挑战。对于实际应用,合理使用编译器参数,结合 Graal 编译器,能够显著优化 Java 服务性能,这也是技术团队在性能优化中的重要方向。
转载地址:http://onryk.baihongyu.com/