16 JVM即时编译(上):JIT编译原理与分层编译

图片[1]-16 JVM即时编译(上):JIT编译原理与分层编译-速优课

即时编译(JIT)是 JVM 提升性能的核心技术。它将频繁执行的字节码编译为本地机器码,显著提高程序的运行速度。本文将深入探讨 JIT 编译的原理、分层编译策略以及编译器的选择。

一、JIT 编译的基本概念

什么是 JIT 编译

JIT(Just-In-Time)编译是指在程序运行时,将字节码动态编译为本地机器码的过程。与静态编译(如 C++)不同,JIT 编译发生在运行时,可以利用运行时信息进行优化。

JIT 编译的优势

  1. 运行时优化:可以根据实际执行情况进行优化
  2. 动态调整:可以根据程序行为调整优化策略
  3. 跨平台性:保持 Java 的跨平台特性

JIT 编译与解释执行的对比

特性解释执行JIT 编译
启动速度慢(需要编译时间)
运行速度快(本地机器码)
内存占用高(需要存储编译后的代码)
优化能力有限强大(可以进行深度优化)

二、热点代码的识别

什么是热点代码

热点代码是指在程序执行过程中被频繁调用的代码,通常包括:

  • 热点方法:被多次调用的方法
  • 热点循环:循环体被多次执行

热点代码的判定

JVM 使用方法调用计数器循环回边计数器来识别热点代码:

  1. 方法调用计数器:记录方法被调用的次数
  2. 循环回边计数器:记录循环体被执行的次数

触发 JIT 编译的条件

当方法调用计数器或循环回边计数器超过阈值时,触发 JIT 编译:

  • 默认阈值:方法调用 10000 次,循环回边 100000 次
  • 可以通过 -XX:CompileThreshold 参数调整

三、分层编译策略

分层编译的目的

分层编译是 Java 7 引入的默认编译策略,旨在平衡启动速度和运行性能:

  1. 快速启动:初始阶段使用解释执行或简单编译
  2. 逐步优化:随着程序运行,逐步进行深度优化

分层编译的层次

层次编译类型优化程度编译速度
Level 0解释执行最快
Level 1C1 编译(简单优化)
Level 2C1 编译(完全优化)中等
Level 3C1 编译(带分析信息)中等
Level 4C2 编译

分层编译的流程

解释执行(Level 0)
    ↓
C1 简单编译(Level 1)
    ↓
C1 完全编译(Level 2)
    ↓
C1 带分析信息编译(Level 3)
    ↓
C2 深度编译(Level 4)

四、即时编译器的选择

C1 编译器(Client Compiler)

设计目标:快速编译,适用于客户端应用

优化策略

  • 简单的优化(内联、常量传播等)
  • 快速生成代码
  • 启动速度快

适用场景

  • GUI 应用
  • 桌面应用
  • 小程序

C2 编译器(Server Compiler)

设计目标:深度优化,适用于服务器端应用

优化策略

  • 复杂的优化(循环展开、向量化等)
  • 生成高度优化的代码
  • 运行速度快

适用场景

  • Web 服务器
  • 数据库服务器
  • 大型企业应用

Graal 编译器

设计目标:通用性和可扩展性

特点

  • 用 Java 实现的编译器
  • 支持多种语言
  • 实验性编译器(Java 10+)

五、JIT 编译的过程

编译触发

  1. 方法调用触发:方法调用次数超过阈值
  2. 循环触发:循环体执行次数超过阈值
  3. 手动触发:使用 JVMCI API 手动触发

编译线程

JVM 使用专门的编译线程进行 JIT 编译:

  • 编译线程与应用线程并行执行
  • 根据 CPU 核数自动调整编译线程数
  • C1 和 C2 编译线程比例为 1:2

编译结果

编译后的机器码存储在代码缓存中:

  • 下次调用同一方法时直接使用编译后的代码
  • 代码缓存大小可以通过参数调整
  • 编译后的代码可以被垃圾回收

六、JIT 编译的监控与调优

JIT 编译的监控

// 使用 jstat 监控 JIT 编译
jstat -compiler <pid>
jstat -printcompilation <pid>
​
// 使用 JVM 参数输出编译日志
java -XX:+PrintCompilation MyApp
java -XX:+PrintCompilation -XX:+PrintInlining MyApp

JIT 编译的调优参数

参数说明默认值
-XX:CompileThreshold触发 JIT 编译的阈值10000
-XX:TieredCompilation是否启用分层编译true
-XX:CICompilerCount编译线程数CPU 核心数
-XX:+UseC1Compiler使用 C1 编译器false
-XX:+UseC2Compiler使用 C2 编译器true
-XX:+UseGraalCompiler使用 Graal 编译器false

编译日志分析

// 编译日志示例
1    java.lang.String::hashCode (60 bytes)
2    java.lang.String::indexOf (156 bytes)
3    java.util.ArrayList::size (5 bytes)   made not entrant
4    java.util.ArrayList::size (5 bytes)   made zombie

关键信息

  • 方法名和字节码大小
  • made not entrant:方法被替换
  • made zombie:方法被废弃

七、总结

JIT 编译是 JVM 提升性能的核心技术:

  1. 基本概念:运行时将字节码编译为本地机器码
  2. 热点识别:通过方法调用计数器和循环回边计数器
  3. 分层编译:平衡启动速度和运行性能
  4. 编译器选择:C1(客户端)、C2(服务器)、Graal(实验性)
  5. 监控调优:使用 jstat 和 JVM 参数

理解 JIT 编译的原理,有助于我们写出更高效的代码,并进行有效的 JVM 调优。

© 版权声明
THE END
喜欢就支持一下吧
点赞9
相关推荐
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容

温馨提示:
1、本内容转载于网络,版权归原作者所有!
2、本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
3、本内容若侵犯到你的版权利益,请联系我们,会尽快给予删除处理!