“代码跑着跑着,就变快了?”——揭秘Java性能幕后引擎:即时编译器

简介: HotSpot虚拟机内置C1和C2两个即时编译器。C1启动快,适合快速执行;C2优化强,适合长期运行。自Java 9起,默认启用C2或分层编译。分层编译结合C1与C2优势,共分5层,逐步提升编译质量。方法调用计数器与循环回边计数器用于识别热点代码,触发JIT编译。循环回边计数器还可启动栈上替换(OSR),提升大循环性能。本文详解JIT编译机制与性能优化策略。

HotSpot虚拟机内部集成了两个即时编译器,分别被称为C1编译器(Client Compiler/ Quick Complier)和C2编译器(Server Compiler)。自Java 9起,-server模式(即启用C2编译器或分层编译)是默认选项,-client选项通常会被忽略。
C1编译器的启动速度较快,主要关注局部的、简单且可靠的优化策略,例如方法内联、常量传播、死代码消除、冗余消除等。相比之下,C2编译器则专注于全局优化,这些优化通常需要更长的编译时间,甚至会根据性能监控(profiling)数据进行一些激进但不一定可靠的优化,例如更复杂的内联决策、逃逸分析、循环优化、向量化等。C2编译器的性能通常比C1编译器高出30%以上,因此更适合长时间运行的后台程序。
从Java 7开始引入,并在Java 8中成为默认策略(当C2可用时),分层编译结合了C1的快速启动和C2的高峰值性能。它将编译过程划分为5个层次。
1)第0层:解释执行收集性能监控数据,主要是方法调用计数器和循环回边计数器。
2)第1层:C1编译器(Simple C1)不进行Profiling,快速编译为本地代码。
3)第2层:C1编译器(Limited Profile C1)进行少量的Profiling(调用次数、循环次数)。
4)第3层:C1编译器(Full Profile C1)进行全面的Profiling,收集包括分支频率、类型信息等更详细的数据,为C2做准备。
5)第4层:C2编译器利用C1收集到的详尽Profiling数据,进行最大程度的优化编译。
性能监控是在程序执行过程中收集反映代码执行状态的数据,如方法调用频率、循环执行频率、分支跳转信息、类型剖面等。这些数据是即时编译器(尤其是C2)做出明智优化决策的依据。性能监控的精度越高,其带来的额外性能开销就越大。最基本的是方法调用计数器和循环回边计数器,用于识别热点代码并触发即时编译。编译阈值是动态的,并且受分层编译策略的影响,但传统的Client模式下默认阈值约为1500次调用,Server模式下约为10000次调用(这些具体数字可能随JDK版本和模式变化)。
image.png

方法调用计数器
方法调用计数器(Invocation counter),顾名思义,这个计数器就是用于统计方法被调用的次数。需注意该计数器统计的非绝对次数,而是衡量一个相对的执行频率。当超过一定的时间限度,如果方法的调用次数仍不足以触发即时编译,那这个方法的调用计数会被减少一半,这个过程称为热度的衰减 (Counter decay),而这段时间就称为此方法统计的半衰周期 (Counter half life time)。

@RequestMapping(value = "/input")
public CommonResponse input(@RequestBody InputRequest request) {
   
     // 如果 input 方法本身成为热点,它会被JIT编译。
     // JIT编译器可能会决定将 doSomething 方法内联到 input 方法中,
     // 如果 doSomething 方法符合内联条件(如方法体小、调用频繁等)。
     return CommonResponse.ok(doSomething(request));
}

public void doSomething(InputRequest request) {
   
     // 如果 doSomething 方法自身被频繁调用(无论是直接调用还是通过 input 间接调用),
     // 并且达到了编译阈值,它也会被JIT编译成本地机器码。
     // ... 复杂的业务逻辑 ...
}

循环回边计数器
循环回边计数器(Loop backEdge counter)会对程序中的循环进行计数。每当程序执行一次循环的回边(即从循环的末尾跳回到循环的开始),循环回边计数器的值就会增加。

void loop() {
   
    int sum = 0;
    for (int i = 0; i < 10; i++) {
   
        sum += i;
    }
}

上面这段代码经过编译生成下面的字节码:

  public void loop();
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_0
       3: istore_2
       4: iload_2
       5: bipush        10
       7: if_icmpge     20
      10: iload_1
      11: iload_2
      12: iadd
      13: istore_1
      14: iinc          2, 1
      17: goto          4
      20: return

在上述字节码中,循环回边计数器被存储在第7行的if_icmpge指令中。if_icmpge指令用于接收两个操作数用于比较计算,以决定循环体跳转的位置。在解释执行时,每当运行一次该指令,该方法的循环回边计数器加1。
循环回边计数器触发的优化编译技术叫作栈上替换 (On stack replacement,OSR) 。假设有一个方法只被调用一次,但却包含超过一万次以上循环迭代次数,这个循环方法无法以方法调用计数来统计。而栈上替换技术解决了这个问题。当编译器检测到一个循环已经迭代次数达到阈值时,动态地将这个循环(以及包含它的方法的一部分)编译成本地机器码,并让当前正在执行的线程“切换”到新编译的代码上继续执行循环,而无需等待方法调用结束。

void largeLoop() {
   
   // 假设此方法只被调用一次
    long sum = 0;
    // 1. 循环回边计数器通过迭代统计,即使方法调用次数少,此循环也会变热。
    // 2. 当达到OSR阈值,JIT会将循环部分编译成本地机器码。
    // 3. 正在执行的线程会从解释执行(或C1代码)的循环“栈上替换”到新编译的C2代码。
    for (int i = 0; i < 100000000; i++) {
    // 非常大的循环次数
        sum += i;
        // ... 其他操作 ...
    }
    System.out.println(sum);
}

未完待续

很高兴与你相遇!如果你喜欢本文内容,记得关注哦!

目录
相关文章
|
Java API Spring
打造未来电商新引擎:揭秘Java可扩展API设计,让支付与物流灵活如丝,引领电商时代潮流!
【8月更文挑战第30天】本文通过电商平台案例,探讨了如何设计可扩展的Java API。首先定义支付和物流服务的接口与抽象类,然后实现具体服务,接着引入工厂模式或依赖注入管理服务实例,最后通过配置实现灵活扩展。这种设计确保了应用架构的灵活性和长期稳定性。
238 3
|
JSON 自然语言处理 Java
这款轻量级 Java 表达式引擎,真不错!
AviatorScript 是一个高性能、轻量级的脚本语言,基于 JVM(包括 Android 平台)。它支持数字、字符串、正则表达式、布尔值等基本类型,以及所有 Java 运算符。主要特性包括函数式编程、大整数和高精度运算、完整的脚本语法、丰富的内置函数和自定义函数支持。适用于规则判断、公式计算、动态脚本控制等场景。
|
自然语言处理 安全 Java
Aviator Java 表达式引擎
AviatorScript 是一门高性能、轻量级寄宿于 JVM 之上的脚本语言。
464 10
|
Java 数据库连接 缓存
Hibernate性能调优:五大秘籍,让应用效能飙升,告别慢如蜗牛的加载,体验丝滑般流畅!
【8月更文挑战第31天】本文深入探讨了提升Hibernate应用性能的五大技巧,包括选择合适的缓存策略、优化查询语句、合理使用Eager与Lazy加载、批量操作与事务管理以及利用索引和数据库优化。通过正确配置多级缓存、分页查询、延迟加载、批量处理及合理创建索引,能够显著提高应用响应速度与吞吐量,改善用户体验。这些技巧需根据具体应用场景灵活调整,以实现最佳性能优化效果。
611 0
|
存储 搜索推荐 算法
Java中的文本搜索与全文检索引擎
Java中的文本搜索与全文检索引擎
|
存储 搜索推荐 Java
Java远程连接本地开源分布式搜索引擎ElasticSearch
Java远程连接本地开源分布式搜索引擎ElasticSearch
226 0
|
存储 关系型数据库 Java
|
缓存 监控 算法
Google Aviator——轻量级 Java 表达式引擎实战
Drools(JBoss Rules )是一个开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
1659 0
Google Aviator——轻量级 Java 表达式引擎实战
|
存储 关系型数据库 MySQL
Java 最常见的面试题:说一下 mysql 常用的引擎?
Java 最常见的面试题:说一下 mysql 常用的引擎?
|
SQL 安全 NoSQL
告别shiro-cas单点登录集成库,这款简单且强壮的Java Web安全引擎pac4j你值得拥有
告别shiro-cas单点登录集成库,这款简单且强壮的Java Web安全引擎pac4j你值得拥有
962 0
告别shiro-cas单点登录集成库,这款简单且强壮的Java Web安全引擎pac4j你值得拥有