深入JVM:详解JIT即时编译器
文章目录
- 深入JVM:详解JIT即时编译器
- 一、序言
- 二、基础概念
- 1、何为JIT即时编译
- 2、热点代码
- 三、HotSpot内置的即时编译器
- 1、C1编译器
- 2、C2编译器
- 3、分层编译
- 3.1 协作流程
- 四、常见JIT优化技术
- 1、方法内联
- 2、逃逸分析
- (1)同步锁消除
- (2)栈上分配
- (3)标量替换
- 五、后记
深入JVM:详解JIT即时编译器
一、序言
对于Java工程师而言,深入理解JVM(Java虚拟机)不仅是掌握Java程序运行机制的基础,也是提升系统性能、优化应用和解决复杂问题能力的重要一步,更是Java进阶之路的重中之重。
本文小豪将带大家认识JIT即时编译器,介绍主流HotSpot虚拟机内置的几款即时编译器,同时结合代码实例,着重讲解JIT优化手段,话不多说,我们直接进入正题。
二、基础概念
在之前,网上可能经常说Java语言的运行速度不如C或C++,Java运行速度慢主要是因为它是解释执行的,而C或C++是编译执行的,解释执行需要通过JVM虚拟机将字节码实时翻译成机器码(边翻译边执行),才能运行在操作系统上,这个过程会比编译执行慢。
但现在再说这个结论就不太对了,随着JIT即时编译技术的发展,性能差距正在逐步缩小,甚至在某些情况下,执行速度是优于C或C++的。
1、何为JIT即时编译
在Java程序执行过程中,主要依靠字节码指令来进行。这些字节码指令按照顺序逐行被实时翻译成机器码,以便于在操作系统上运行。当某些方法或代码块(它们都对应特定的字节码)被频繁调用时,这部分代码就被视为热点代码。
JVM虚拟机会针对性的对这部分热点代码进行优化编译,将它们从字节码转换为本地机器码,然后将优化后的本地机器码缓存起来,后续再执行时可以直接从缓存中获取并运行,无需再次编译。
完成这个过程的编译器,就称为JIT即时编译器(Just In Time Compiler)。JIT即时编译器显著提升了Java的性能,缩小了与其它编译型语言运行速度的差距。
2、热点代码
热点代码是指在运行过程中被频繁执行的代码,被即时编译的热点代码有两种,分别是:
- 被多次调用的方法
- 被多次执行的循环体
而热点代码由热点探测进行发现,热点探测基于计数器,JVM虚拟机会为每个方法建立对应的计数器,统计方法的执行次数、方法内的循环次数等,如果计数器超过指定阈值,则标识其为热点代码。
方法调用计数器:统计方法被调用的次数
回边计数器:统计方法内循环体代码执行的次数
三、HotSpot内置的即时编译器
主流的HotSpot虚拟机内置了两个JIT编译器:C1(Client Compiler)编译器和C2(Server Compiler)编译器,在常用的JDK 8版本中,C1和C2是相互协同工作的。
1、C1编译器
C1编译器注重启动时间和编译时间,编译速度较快,但优化程度相对较低。
C1的优化策略相对简单,也比较轻量级,比如方法内联、公共子表达式消除,C1编译器编译的代码的执行速度通常比C2编译器慢。
2、C2编译器
C2编译器侧重于深度优化,与C1正好相反,C2编译器的编译时间较长,但优化的程度较高。
C2的优化策略比较深度,会进行更高级的优化,比如逃逸分析等,C2编译器编译的代码的执行速度通常比C1编译器快。
C2编译器由于深度优化代码过于复杂,已经很难维护了,从JDK 10开始,Graal编译器已经代替了C2编译器,与C1编译器协同工作
3、分层编译
由于C1和C2编译器在优化方面有不同的侧重点:C1侧重编译速度,C2侧重深度优化。
从JDK 7开始,采用分层编译的方式,C1和C2相互协同,共同发挥作用,HotSpot虚拟机根据代码的运行性能动态的选择具体采用哪个编译器完成优化。
在分层编译中,整个优化过程分为五个层次,对应着不同的优化策略:
- 第0层:解释器执行,开启
Profiling
性能监控功能 - 第1层:C1编译器执行,不开启
Profiling
,执行不带Profiling
功能的C1编译代码 - 第2层:C1编译器执行,开启
Profiling
,仅执行带部分Profiling
功能的C1编译代码(方法调用次数和循环次数) - 第3层:C1编译器执行,开启
Profiling
,执行带全部Profiling
功能的C1编译代码 - 第4层:C2编译器执行,执行C2编译代码
Profiling
性能监控是指在程序执行过程中,收集程序执行的各项数据,包括方法调用次数、循环次数、类型转换等等,收集的数据越多,额外的性能开销越大。第1层到第3层都是由C1编译器执行的,显然,第1层由于不开启
Profiling
,执行性能会高于第2层,同时第2层只收集少量数据,第2层的执行性能也会高于第3层(总体性能:第4层 > 第1层 > 第2层 > 第3层 > 第0层)
3.1 协作流程
在分层编译中,C1和C2编译器是相互协作的,它们都由各自独立的线程来处理编译任务,线程内部维护着任务队列,用来存放待编译的热点代码,具体协作流程如下:
- 默认情况下,热点方法首先会被第3层的C1编译器执行,当
Profiling
性能监控收集到的数值达到阈值,JVM会评估C1和C2的优化性能(字节码数较少等)- 若判定C1和C2的执行效率相当:交由第1层的C1编译器进行优化,停止
Profiling
监控,不再收集运行信息 - 若判定C2执行效率相当优于C1:交由第4层的C2编译器进行深度优化
- 若判定C1和C2的执行效率相当:交由第1层的C1编译器进行优化,停止
- C1线程忙碌时,会直接交由第4层的C2编译器进行深度优化
- C2线程忙碌时,会先交由第2层的C1编译器执行优化,进行信息的初步收集,待C2线程空闲时,再交由第3层的C1编译器执行
四、常见JIT优化技术
JIT即使编译器主要是通过方法内联和逃逸分析两种技术来实现代码的优化。
1、方法内联
方法内联其实就是将被调用方法的字节码直接插入到调用该方法的地方,调用时减少了栈帧的创建销毁开销。
举个例子:
实际上JIT修改的是字节码指令,为便于理解,本文使用代码举例
// 方法内联
public double methodInlining() {double num1 = Math.random();double num2 = Math.random();// 等价于 -> double sum = num1 + num2double sum = add(num1, num2);return sum;
}public double add(double num1, double num2) {return num1 + num1;
}
在代码中,方法内联会将其中的add(num1, num2)
方法转换为实际的num1 + num1
,直接进行计算操作,避免了方法调用。
这里我们简单测试一下:
public static void main(String[] args) {long startTime = System.currentTimeMillis();JitTest demo = new JitTest();for (int i = 0; i < 1000000; i++) {demo.methodInlining();}long endTime = System.currentTimeMillis();System.out.println("执行耗时:" + (endTime - startTime));
}
测试代码也比较简单,循环调用百万次,统计总耗时。
首先我们测试不启用JIT优化下这段代码的执行耗时,禁用JIT优化只需要添加JVM参数-Xint
即可,禁用后,控制台输出:
执行耗时:881
取消掉禁用参数-Xint
,启用JIT优化,重新执行这段测试代码:
执行耗时:47
我们进一步验证一下add
方法是否进行了方法内联优化,添加三个JVM参数-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
,打印JVM优化内容:
- -XX:+PrintCompilation:输出被JIT编译的方法信息
- -XX:+UnlockDiagnosticVMOptions:解锁用于诊断JVM的选项,默认关闭
- -XX:+PrintInlining:打印内联方法
如图,大量方法都被内联,其中也包括我们自己写的methodInlining
和add
方法,并且同时打印出了方法对应的字节大小,我们发现这些被内联的方法字节数都比较小。
其实这是由于方法内联也有一点的限制,即字节数过大的方法体不会进行内联,具体限制如下:
-
非热点代码:默认限制字节数小于
35
,可通过参数调整限制// 非热点代码的内联字节大小最大阈值 -XX:MaxInlineSize=n
-
热点代码:默认限制字节数小于
325
,可通过参数调整限制// 热点代码的内联字节大小最大阈值 -XX:FreqInlineSize=n
-
接口实现限制:若接口的实现方法数量超过3个,则不会被内联
2、逃逸分析
逃逸分析核心思想是判断方法内创建的对象是否会被外部所引用,当JIT编译器判断到对象在方法外部不会被引用,即该对象的生命周期被限定在当前方法执行期间,编译器就能采取多种优化措施,比如锁消除、标量替换和栈上分配。
(1)同步锁消除
同步锁消除旨在消除代码中的同步操作,若某个对象不会逃逸出当前方法,只在方法内部使用,则不存在线程安全问题,因为每个方法的栈都是私有的,JIT编译器就会消除该同步锁操作,提高程序的执行效率。
举个例子:
public void syncLockEliminate() {// 等价于 -> 不执行该同步锁synchronized (JitTest.class) {// 业务代码逻辑}
}public static void main(String[] args) {long startTime = System.currentTimeMillis();JitTest demo = new JitTest();for (int i = 0; i < 1000000; i++) {demo.syncLockEliminate();}long endTime = System.currentTimeMillis();System.out.println("执行耗时:" + (endTime - startTime));
}
这里启用和禁用JIT锁消除(禁用锁消除参数:-XX:-EliminateLocks
)对应控制台输出的执行耗时分别为:
// 启用锁消除
执行耗时:143// 禁用锁消除
执行耗时:203
(2)栈上分配
栈上分配是将被创建的对象内存分配在栈上,而不是堆上,降低GC垃圾回收的频率。
举个例子:
static class UserInfo {// 姓名private String name;// 年龄private int age;public UserInfo() {}public UserInfo(String name, int age) {this.name = name;this.age = age;}
}// 栈上分配,不赋值对象属性
public static void createUser() {UserInfo user = new UserInfo();
}public static void main(String[] args) throws Exception {long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {createUser();}long endTime = System.currentTimeMillis();System.out.println("执行耗时:" + (endTime - startTime));
}
这里我们验证一下这个结论,我们在JVM参数上添加-XX:+PrintGC
,输出GC日志信息,同时将堆内存空间设小一些:
-Xms10m -Xmn10m -XX:+PrintGC
首先默认启用JIT逃逸分析优化,控制台输出:
执行耗时:72
接着禁用逃逸分析优化,添加参数-XX:-DoEscapeAnalysis
关闭逃逸分析,控制台输出:
[GC (Allocation Failure) 7679K->1041K(9216K), 0.0010975 secs]
[GC (Allocation Failure) 8721K->904K(9216K), 0.0006550 secs]
[GC (Allocation Failure) 8584K->840K(9216K), 0.0007822 secs]
执行耗时:103
关闭逃逸分析优化后,JVM执行了几次的GC垃圾回收。
(3)标量替换
标量替换是将需要被创建的对象拆解成独立的标量(对象内部的基本数据类型),方法执行时不创建该对象实例,而是直接创建它的成员变量代替它,这些成员变量作为该方法的局部变量被分配在栈上。
标量替换是针对栈上分配更进一步的优化技术,栈上分配只是将对象从堆上分配到栈上,而标量替换是将对象进行拆解,作为方法的局部变量被分配在栈上。
举个例子:
// 标量替换,赋值对象属性
public static void createUser() {// 等价于 -> String name = user.name = xiaohao;// int age = user.name = 24UserInfo user = new UserInfo("xiaohao", 24);String userInfo = user.name + ":" + user.age;
}public static void main(String[] args) throws Exception {long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {createUser();}long endTime = System.currentTimeMillis();System.out.println("执行耗时:" + (endTime - startTime));
}
若JIT判断到UserInfo
对象不会逃逸出当前方法,则会将UserInfo
对象的name
和age
属性替换为两个局部变量,不进行创建UserInfo
对象,避免在堆上分配对象实例。
控制台输出的执行耗时分别为:
// 启用逃逸分析
执行耗时:162// 禁用逃逸分析
执行耗时:203
五、后记
本文从JIT即时编译器基础概念开始介绍,着重讲解了常见的JIT优化技术,包括方法内联与逃逸分析,额外拓展了HotSpot虚拟机内置的C1、C2编译器及分层编译协作流程。
JIT即时编译器在运行时对Java字节码进行优化,极大地提升了Java程序的执行效率。不过也要求我们日常编写代码要注意一定的规范,让JIT发挥最大效果。比如控制方法的代码行数,避免写过长的代码方法,可以将复杂的逻辑拆分成多个小方法,提升方法内联的生效几率;另外也可以控制接口的实现数量,尽量不超过两个,降低方法内联的复杂度;以及注意在方法内创建对象时,尽量不让对象逃逸。
如果大家觉得内容有价值,不妨考虑点点赞,关注关注小豪,后续小豪将会继续更新其它Java相关文章,大家共同进步~
相关文章:

深入JVM:详解JIT即时编译器
文章目录 深入JVM:详解JIT即时编译器一、序言二、基础概念1、何为JIT即时编译2、热点代码 三、HotSpot内置的即时编译器1、C1编译器2、C2编译器3、分层编译3.1 协作流程 四、常见JIT优化技术1、方法内联2、逃逸分析(1)同步锁消除(…...

ORBSLAM3_ROS_Ubuntu18_04环境搭建安装
orbslam3安装 ORB-SLAM3配置及安装教程(2023.3)_orbslam3安装-CSDN博客 换源,换成国内的 搜索software 安装工具 sudo apt install git sudo apt update sudo apt install gcc g cmake安装 cmake安装新版本 ubuntu20.04安装cmake详细…...

【opencv - C++ - Ubuntu】putText 显示中文最快方法
话不多说,直接上代码 #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/freetype.hpp>using namespace std; using namespace cv;int main(void) {Mat image(1000, 1800, CV_8UC3, Scalar(200,162,33));Ptr<freetype::F…...

百度网盘下载速度慢的解决办法
目录 一、背景 二、解决办法 1、点击三个竖点,再点设置 2、点击传输,再点击去开启该功能 3、点击同意,开启优化速率 三、结果 四、备注 一、背景 当你不是百度网盘会员时,你在使用百度网盘下载时,是否下载速度太…...
Python api接口 异步
Python API接口异步编程简介 在现代的软件开发中,大多数应用都需要通过API接口与其他系统进行交互。Python是一种非常流行的编程语言,因此许多开发者选择使用Python来构建他们的API接口。在一些情况下,API接口需要进行异步编程,以…...
Java 和 Kotlin 单例模式写法对比
目录 1、饿汉模式 Java 写法: Kotlin 写法: Kotlin 这段代码反编译&简化后如下: 2、懒汉模式,静态同步方法 Java 写法: Kotlin 写法: Kotlin 这段代码反编译&简化后如下: 3、懒…...
解析connectionReset异常的原因与解决方案
解析connectionReset异常的原因与解决方案 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨Java中connectionReset异常的原因及其解决方案。这…...

mindspore打卡第9天 transformer的encoder和decoder部分
mindspore打卡第9天 transformer的encoder和decoder部分 import mindspore from mindspore import nn from mindspore import ops from mindspore import Tensor from mindspore import dtype as mstypeclass ScaledDotProductAttention(nn.Cell):def __init__(self, dropout_…...
Python实现IPv4地址和16进制互相转换
Python实现IPv4地址和16进制互相转换 import socketdef ip_to_hex16(ipaddr):# 使用 socket 库中的方法将IP地址转换为网络字节序的二进制表示hex_bytes socket.inet_aton(ipaddr)# 将二进制数据转换为整数, 其中byteorderbig 表示使用大端字节序(从高位到低位&…...

计算机视觉 | 基于图像处理和边缘检测算法的黄豆计数实验
目录 一、实验原理二、实验步骤1. 图像读取与预处理2. 边缘检测3. 轮廓检测4. 标记轮廓序号 三、实验结果四、完整代码 Hi,大家好,我是半亩花海。 本实验旨在利用 Python 和 OpenCV 库,通过图像处理和边缘检测算法实现黄豆图像的自动识别和计…...

深入分析 Android BroadcastReceiver (七)
文章目录 深入分析 Android BroadcastReceiver (七)1. 高级应用场景1.1 示例:动态权限请求1.2 示例:应用内通知更新 2. 安全性与性能优化2.1 示例:设置权限防止广播攻击2.2 示例:使用 LocalBroadcastManager2.3 示例:在…...

C++中的数据结构
一.STL标准库 结构:STL中有六大组件,分别是:容器,算法,迭代器,仿函数,配接器,配置器;以下分别介绍这六大组件中的最主要的三个。 1.容器 容器来配置存储空间,算法通过…...

武汉星起航:一站式服务,助力亚马逊卖家高效运营,实现收益飞跃
在跨境电商的浪潮中,武汉星起航电子商务有限公司以其独特的一站式跨境电商服务,为众多亚马逊卖家提供了强有力的支持,助力他们在不断发展的市场中脱颖而出,实现收益的大幅提升。 武汉星起航的一站式跨境电商服务,以其…...

从灵感到实践:Kimi辅助完成学术论文选题的文艺之旅
学境思源,一键生成论文初稿: AcademicIdeas - 学境思源AI论文写作 昨天我们为大家介绍了ChatGPT辅助完成实现设计(AI与学术的交响:ChatGPT辅助下的实验设计新篇章)。今天我们再来看看Kimi对于论文选题都能提供哪些帮助…...
华为od-C卷200分题目4 -电脑病毒感染
华为od-C卷200分题目4 -电脑病毒感染 一个局域网内有很多台电脑,分别标注为0 - N-1的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用t表示。其中网络内一个电脑被病毒感染,其感染网络内所有的电脑需要最少需要多长…...

show-overflow-tooltip 解决elementui el-table标签自动换行的问题
elementui中 el-table中某一行的高度不想因为宽度不够而撑开换行展示的解决方法。可通过show-overflow-tooltip属性解决,如下 代码是这样的 <el-table-column width"80" prop"id" label"ID"></el-table-column> <el…...

数字社交的领航者:解析Facebook的引领作用
在当今数字化社会中,社交网络已经成为了人们日常生活不可或缺的一部分。而在众多社交平台中,Facebook凭借其巨大的用户基础和创新的技术应用,被公认为数字社交领域的领航者之一。本文将深入解析Facebook在数字社交中的引领作用,探…...
深度分析 Apache Flink 窗口机制
什么是 Flink 窗口? Apache Flink 是一个用于处理实时流数据的开源框架,其核心功能之一是窗口(Window)机制。窗口是 Flink 在处理流数据时用于划分数据流的逻辑概念,它将无限的流数据切割成有限的、可管理的部分&…...
ubuntu 软链接(ubuntu20.04)
ubuntu 软链接(ubuntu20.04) 在Ubuntu和其他Linux系统中,软链接(也称为符号链接)是文件系统中的一个特殊类型的文件,它作为一个引用或指针,指向另一个文件或目录。软链接类似于Windows中的快捷…...

如何在LabVIEW中使用FPGA模块
LabVIEW FPGA模块是NI公司推出的一款强大工具,它允许用户使用LabVIEW图形化编程环境来开发FPGA(现场可编程门阵列)应用程序。与传统的HDL(硬件描述语言)编程相比,LabVIEW FPGA模块大大简化了FPGA开发的过程…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...