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版本和模式变化)。

方法调用计数器

方法调用计数器(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性能幕后引擎:即时编译器的更多相关文章

  1. 你的C#代码是怎么跑起来的(二)

    接上篇:你的C#代码是怎么跑起来的(一) 通过上篇文章知道了EXE文件的结构,现在来看看双击后是怎样运行的: 双击文件后OS Loader加载PE文件并解析,在PE Optional Header里找 ...

  2. Vue3.0系列——「vue3.0性能是如何变快的?」

    前言 先学习vue2.x,很多2.x内容依然保留: 先学习TypeScript,vue3.0是用TS重写的,想知其然知其所以然必须学习TS. 为什么学习vue3.0? 性能比vue2.x快1.2-2倍 ...

  3. 【转】【翻】Android Design Support Library 的 代码实验——几行代码,让你的 APP 变得花俏

    转自:http://mrfufufu.github.io/android/2015/07/01/Codelab_Android_Design_Support_Library.html [翻]Andro ...

  4. Android Design Support Library 的 代码实验——几行代码,让你的 APP 变得花俏

    原文:Codelab for Android Design Support Library used in I/O Rewind Bangkok session--Make your app fanc ...

  5. Python3.7.2,在Linux上跑来跑去的,是在升级打怪么?

    Python3.7.2,在Linux上跑来跑去的,是在升级打怪么?   前不久,发布了Python在Windows(程序员:Python学不学?完全没必要纠结)和Mac OS(我是Python,P派第 ...

  6. 快排的java实现方式,用java代码来实现快排

    1. 快排的思想 通过一趟排序将要排序的数据分割成独立的两部分,前一部分的所有数据都要小于后一部分的所有数据,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据的 ...

  7. 资源很多,你却不会使用——以不变应万变才是自学Java的正确方法

    鄙人乐于寻找学习方法,在这里提出自己的见解,希望可以帮助想玩好Java而又感觉很难上手的同学对Java不再恐惧 现状 我们的同学们除了某月,某婷等等大神以外,想必仍然存在着一大批同学根本没有摸索到学习 ...

  8. Senna.js – 速度极快的单页应用程序引擎

    Senna.js 是一个速度超快的单页应用程序引擎,提供了几个低级别的 API,可以帮助你打造现代化的基于 Web 的应用程序.更重要的是,搜索引擎蜘蛛应该能够索引相同的内容. 通过使用 HTML5 ...

  9. Java 性能优化手册 — 提高 Java 代码性能的各种技巧

    转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...

  10. CLR via C#(01)-.NET平台下代码是怎么跑起来的

    1. 源代码编译为托管模块 程序在.NET框架下运行,首先要将源代码编译为托管模块.CLR是一个可以被多种语言所使用的运行时,它的很多特性可以用于所有面向它的开发语言.微软开发了多种语言的编译器,编译 ...

随机推荐

  1. C#快速排序算法实现及循环条件细节思考

    C#快速排序算法实现及循环条件细节思考 快速排序是一种分治思想的递归排序算法,其基本思想为: 在每一步中,挑选一个主元(pivot)出来,比如第一个元素 然后遍历除主元外的剩下的元素,把所有小于主元的 ...

  2. 【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(4)

    1.问题描述: LoginWithHuaweiIDButton不支持深色模式下定制文字和loading样式? 解决方案: LoginWithHuaweiIDButtonParams 中的有个suppo ...

  3. React Native开发鸿蒙Next---图片浏览与保存的问题交流

    React Native开发鸿蒙Next---图片浏览与保存的问题交流 之前介绍过利用鸿蒙三方RN组件@react-native-camera-roll/camera-roll保存图片到相册. Rea ...

  4. `.NC`文件的读取与使用

    .NC文件的读取与使用 前言 NetCDF(network Common Data Form)网络通用数据格式是一种面向数组型并适于网络共享的数据的描述和编码标准.目前,NetCDF广泛应用于大气科学 ...

  5. TypeScript中never类型的实用技巧

    本文由 ChatMoney团队出品 妙用一 当我们在一个项目中,可能会去改动一个在整个项目中应用很广泛的函数的参数类型,但是可能由于代码量比较庞大,我们不好排查改了之后哪些地方会出现问题,此时我们可以 ...

  6. 后端性能-batch 化的想法

    项目中我们提高性能或者吞吐经常使用的是 batch 化,比如说获取帐号信息,我们1条1条查询可能不如我们一次查询100条性能高. 有的时候想这样是为什么呢?因为单个请求中间有网络往返.网络延迟等原因会 ...

  7. 赴一场开源盛会丨10月29日 COSCon'22 开源年会杭州分会场,这里只差一个「你」!

    报名地址:https://www.bagevent.com/event/8322877 2022年,世界正在改变,开源创造价值.已经办到第七届的开源年会首次来到杭州与开发者们相聚.你眼中的开源是怎样的 ...

  8. Nginx 本地代理转发请求 502 Bad Gateway

    问题 在使用 yum 安装 nginx 后可能会出现配置完成后却无法访问的问题,查看 audit.log 会发现类似于以下的错误信息 原因 出现此问题的原因是 SELinux 基于最小权限原则默认拦截 ...

  9. 迁移git:gitlab->gitea

    本文目的:迁移gitlab代码到gitea. 一:创建一个组织: 二:创建git仓库 1:在主界面创建仓库:注意选择拥有者 创建git库需要注意拥有者选择创建的组织.不然后面git链接会出现用户名.其 ...

  10. Blazor学习之旅 (14) Blazor WebAssembly

    在上一篇我们学习了如何创建和使用Razor类库,这一篇我们了解下WebAssembly是什么,以及创建第一个Blazor WebAssembly应用. 什么是WebAssembly? WebAssem ...