架构师杂谈JVM之JIT

关于Java的JIT(即时编译器)知识整理 前言 参考资料 jvm调优之分层编译 关于Java的JIT知识整理 Java分层编译,深入解析java虚拟机:编译概述,即时编译技术 阅读详情

JIT技术是JVM中最重要的核心模块之一。我的课程里本来没有计划这一篇,但因为不断有朋友问起,Java到底是怎么运行的?既然Hotspot是C++写的,那Java是不是可以说运行在C++之上呢?为了澄清这些概念,我才想起来了加了这样一篇文章,算做番外篇吧。

Just In Time

Just in time编译,也叫做运行时编译,不同于 C / C++ 语言直接被翻译成机器指令,javac把java的源文件翻译成了class文件,而class文件中全都是Java字节码。那么,JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行,这种方法就是解释执行。

还有一种,就是把这些Java字节码重新编译优化,生成机器码,让CPU直接执行。这样编出来的代码效率会更高。通常,我们不必把所有的Java方法都编译成机器码,只需要把调用最频繁,占据CPU时间最长的方法找出来将其编译成机器码。这种调用最频繁的Java方法就是我们常说的热点方法(Hotspot,说不定这个虚拟机的名字就是从这里来的)。

这种在运行时按需编译的方式就是Just In Time。

主要技术点

其实JIT的主要技术点,从大的框架上来说,非常简单,就是申请一块既有写权限又有执行权限的内存,然后把你要编译的Java方法,翻译成机器码,写入到这块内存里。当再需要调用原来的Java方法时,就转向调用这块内存。

我们看一个例子:

#include<stdio.h>

int inc(int a) {
    return a + 1;
}

int main() {
    printf("%d\n", inc(3));
    return 0;
}

上面这个例子很简单,就是把3加1,然后打印出来,我们通过以下命令,查看一下它的机器码:

# gcc -o inc inc.c
# objdump -d inc

然后在这一堆输出中,可以找到 inc 方法最终被翻译成了这样的机器码:

  40052d:	55                   	push   %rbp
  40052e:	48 89 e5             	mov    %rsp,%rbp
  400531:	89 7d fc             	mov    %edi,-0x4(%rbp)
  400534:	8b 45 fc             	mov    -0x4(%rbp),%eax
  400537:	83 c0 01             	add    $0x1,%eax
  40053a:	5d                   	pop    %rbp
  40053b:	c3                   	retq 

我来解释一下(读者需要一定的x86汇编语言的知识)。

第一句,保存上一个栈帧的基址,并把当前的栈指针赋给栈基址寄存器,这是进入一个函数的常规操作。我们不去管它。

第三句,把edi存到栈上。在x64处理器上,前6个参数都是使用寄存器传参的。第一个参数会使用rdi,第二个参数使用 rsi,等等。所以 edi 里存的其实就是第一个参数,也就是整数 3,为什么使用rdi的低32位,也就是 edi 呢?因为我们的入参 a 是 int 型啊。大家可以换成 long 型看看效果。

第四句,把上一步存到栈上的那个整数再存进 eax 中。

第五句往后,把 eax 加上 1, 然后就退栈,返回。按照x64的规定(ABI),返回值通过eax传递。

我们看到了,其实第三句,第四句好像根本没有存在的必要,gcc 默认情况下,生成的机器码有点傻,它总要把入参放到栈上,但其实,我们是可以直接把参数从 rdi 中放入到 rax 中的。不满意。那我们可以自己改一下,让它更精简一点。怎么做呢?答案就是运行时修改 inc 的逻辑。

#include<stdio.h>
#include<memory.h>
#include<sys/mman.h>

typedef int (* inc_func)(int a); 

int main() {
    char code[] = { 
        0x55,             // push rbp
        0x48, 0x89, 0xe5, // mov rsp, rbp
        0x89, 0xf8,       // mov edi, eax
        0x83, 0xc0, 0x01, // add $1, eax
        0x5d,             // pop rbp
        0xc3              // ret
    };  

    void * temp = mmap(NULL, sizeof(code), PROT_WRITE | PROT_EXEC,
            MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 

    memcpy(temp, code, sizeof(code));
    inc_func p_inc = (inc_func)temp;
    printf("%d\n", p_inc(7));

    return 0;
}

在这个例子中,我们使用了 mmap 来申请了一块有写权限和执行权限的内存,然后把我们手写的机器码拷进去,然后使用一个函数指针指向这块内存,并且调用它。通过这种方式我们就可以执行这一段手写的机器码了。

运行一下看看:

# gcc -o inc inc.c 
# ./inc
8

再回想一下这个过程。我们通过手写机器码把原来的 inc 函数代替掉了。在新的例子中,我们是使用程序中定义的数据来重新造了一个 inc 函数。这种在运行的过程创建新的函数的方式,就是JIT的核心操作。

解释器,C1和C2

在Hotspot中,解释器是为每一个字节码生成一小段机器码,在执行Java方法的过程中,每次取一条指令,然后就去执行这一个指令所对应的那一段机器码。256条指令,就组成了一个表,在这个表里,每一条指令都对应一段机器码,当执行到某一条指令时,就从这个表里去查这段机器码,并且通过 jmp 指令去执行这段机器码就行了。

这种方式被称为模板解释器。

模板解释器生成的代码有很多冗余,就像我们上面的第一个例子那样。为了生成更精简的机器码,我们可以引入编译器优化手段,例如全局值编码,死代码消除,标量展开,公共子表达式消除,常量传播等等。这样生成出来的机器码会更加优化。

但是,生成机器码的质量越高,所需要的时间也就越长。JIT线程也是要挤占Java 应用线程的资源的。所以C1是一个折衷,编译时间既不会太长,生成的机器码的指令也不是最优化的,但肯定比解释器的效率要高很多。

如果一个Java方法调用得足够频繁,那就更值得花大力气去为它生成更优质的机器码,这时就会触发C2编译,c2是一个运行得更慢,但却能生成更高效代码的编译器。

由此,我们看到,其实Java的运行,几乎全部都依赖运行时生成的机器码上。所以,对于文章开头的那个问题“Java是运行在C++上的吗?”,大家应该都有自己的答案了。这个问题无法简单地回答是或者不是,正确答案就是Java的运行依赖模板解释器和JIT编译器。

多说一点优化

我们这节课所举的例子中,可以做更多的优化,例如,既然我进到inc函数以后,完全没有使用栈,那其实,我就不要再为它开辟栈帧了。所以可以把push rbp, pop rbp的逻辑都去掉。

进一步优化成这样:

    char code[] = { 
        0x89, 0xf8,       // mov edi, eax
        0x83, 0xc0, 0x01, // add $1, eax
        0xc3              // ret
    }; 

可以看到,指令更加精简了。我们重新编译运行,还是能成功打印出8。

根据这个问题:为什么 lea 会被用来计算?

我们还可以写出更优化的代码来:

    char code[] = { 
        0x8d, 0x47, 0x01,    // lea 0x1(rdi), rax
        0xc3                 // ret
    };

如果开启 gcc 的优化编译,我们也可以得到这样的代码,例如,还是针对这个方法:

int inc(int a) {
    return a + 1;
}

使用 -O2 优化:

# gcc -o inc inc.c -O2
# objdump -d inc

就可以看到,inc 的机器码变成这样了:

00000000004005f0 <inc>:
  4005f0:	8d 47 01             	lea    0x1(%rdi),%eax
  4005f3:	c3                   	retq 

这和我们手写的优化的机器码是完全一样的了。

实际上,C1和C2所要做的和gcc的优化编译是一样的,就是使用特定的方法生成更高效的机器码。但是从原理上来说,运行时生成机器码这个技术,大家都是相通的。

最后,补充一句,iOS禁掉了JIT编译,所用的手段就是无法申请一块同时具有写权限和执行权限的内存。那么,JIT的核心基石,运行时生成可执行的机器码就无法存在了。

Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框架构思 最近因为工作原因需要使用Qt开发一个类似于Visio的流程图组件,需满足基本的流程图功能,图元类型可自定义扩展,不同图元存有各类自定义数据,可以保存为xml文件,可以读取xml文件生成流程图等等。 阅读详情

相关推荐

DVWA全级别详细通关教程

dvwa全级别详细通关教程,暴力破解,命令注入,CSRF跨站请求伪造,文件包含,文件上传​,SQL注入,XSS

m0_62063669的博客 1万+

【java】Java即时编译(JIT)器原理解析及实践

1.概述 转载:Java即时编译(JIT)器原理解析及实践 一、导读 常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java通常不如C++这类编译型语言。 为了优化Java的性能 ,JVM在解释器之外引入了即时(Just In Time)编译器:当程序运行时,解释器首先发挥作用,代码可以直接执

九师兄 3644

使用freesurfer和3Dslicer进行脑区分割和电极定点(详细版)

1、安装好matlab和freesurfer2、在目标位置创建文件夹3、开放权限4、将需要计算的nii文件放入文件夹。

zQIANYUN的博客 5629

JVM探险-JIT技术

在现代软件开发领域,Java 虚拟机(Java Virtual Machine,JVM)以其高性能和跨平台特性而闻名。其中一个关键的性能优化技术便是 Just-In-Time(JIT)编译器,它在 Java 程序运行时动态地将字节码转换为机器语言,从而极大地提高了程序的执行效率。在现代软件开发领域,Java 虚拟机(Java Virtual Machine,JVM)以其高性能和跨平台特性而闻名。

NarutoConanKing的博客 1960

简易电梯控制系统设计(两层楼)(

(电子科技大学 数字系统实验二 结课验收项目 ) 题目要求:

m0_48801027的博客 1万+

在 FlexSim 中建 AGV 小车模型时,如果设定“每次充电需要 2 小时才能充满”,应该如何配置 AGV 的充电功能?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值。

**My Coding Family** 881

JVM之JIT

JIT技术是JVM中最重要的核心模块之一。我的课程里本来没有计划这一篇,但因为不断有朋友问起,Java到底是怎么运行的?既然Hotspot是C++写的,那Java是不是可以说运行在C++之上呢?为了澄清这些概念,我才想起来了加了这样一篇文章,算做番外篇吧。 Just In Time Just in time编译,也叫做运行时编译,不同于 C / C++ 语言直接被翻译成机器指令,...

weixin_30721899的博客 259

【PCIe 总线及设备入门学习专栏 6.2 -- PCIe VDM (Vendor Defined Messages)】

在PCIe协议中,Vendor Defined Messages (VDMs) 是一种允许厂商定义和传输特定功能的消息类型。这种机制扩展了标准 PCIe 消息的功能,可以用于实现供应商自定义的功能,例如固件更新、设备配置和诊断信息传输。PCIe VDM 是通过实现的,它遵循 PCIe 协议栈的结构,包括事务层、数据链路层和物理层。

CodingCos的博客 1898

JIT——即时编译的原理

介绍 java 作为静态语言十分特殊,他需要编译,但并不是在执行之前就编译为本地机器码。 所以,在谈到 java的编译机制的时候,其实应该按时期,分为两个部分。一个是 javac指令 将java源码变为 java字节码的静态编译过程。 另一个是 java字节码编译为 本地机器码的过程,并且因为这个过程是在程序运行时期完成的所以称之为即时编译。 静态编译过程,通过javac 完成,而即时编译...

weixin_30781631的博客 412

matplotlib保存图片设置分辨率总是96dpi,好烦躁,教你一招轻松解决

matplotlib保存图片设置分辨率总是96dpi,好烦躁,教你一招轻松解决 目录 matplotlib保存图片设置分辨率总是96dpi,好烦躁,教你一招轻松解决 #使用plt.savefig("cm.png",dpi = 300)为什么不管用 #plt.rcParams也不管用 #解决方案 在做论文的过程中,许多期刊或者会议对图片的分辨率有明确的要求,例如,dpi=300。 之前画曲线图都是采取保存方式为savefig(dpi=300),然后查看保存图片的属性信息,然而你看到的现象是

data+scenario+science+insight 8800

数字盲水印嵌入和提取的实现基于小波变换DWT算法

数字盲水印是指在不知道原始图像的情况下,在其上嵌入水印并在后续的提取过程中能够恢复出原始水印的一种技术。本文介绍的数字盲水印嵌入和提取的实现方法基于小波变换DWT算法。具体来讲,DWT将信号或图像进行二次下采样和卷积滤波,使得高频分量的图像信息被集中在一个小区域内,方便进行嵌入和提取操作。其中 a 表示嵌入强度,而 watermark 则是用于嵌入的水印图像,这里我们嵌入的是一个全灰的图像。通过以上的代码,我们可以实现数字盲水印的嵌入和提取,并在一定程度上保护数字图片的版权安全。

HackWhisper的博客 512

java jit 原理_java的JIT 工作原理简单介绍

java的JIT 工作原理简单介绍1.JIT的工作原理图工作原理当JIT编译启用时(默认是启用的),JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码。通常javac将程序源代码编译,转换成java字节码,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行...

weixin_31361715的博客 6269

Qt入门教程【数据类型篇】数值类型

Qt中的数值类型

小鱼酱的博客 1万+

JIT深度剖析

跨语言(语言无关性):JVM只识别字节码,所以JVM其实跟语言是解耦的,也就是没有直接关联,JVM运行不是翻译Java文件,而是识别class文件,这个一般称之为字节码。还有像Groovy 、Kotlin、Scala等等语言,它们其实也是编译成字节码,所以它们也可以在JVM上面跑,这个就是JVM的跨语言特征。Java的跨语言性一定程度上奠定了非常强大的java语言生态圈。 解释执行与JIT Java程序在运行的时候,主要就是执行字节码指令,一般这些指令会按照顺序解释执行,这种就是解释执行。

xjk201的博客 1480

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

JAVA-JIT

一 JIT JIT全程Java Intime Compiler,即Java即时编译器. javac会把.java文件编译成.class文件,所以我们说Java是编译型语言。当然Java是强类型的语言,通常我们说强类型的是编译型的,弱类型的脚本语言(也叫动态语言,相对应的强类型语言叫静态语言)。.class文件格式就是“字节码” JIT的动机基于“二八定律”,20%的热点代码占据了程序80%的执行时间. ...

想吃大盘鸡 2526

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Java即时编译(JIT)原理与调优

Just-In-Time (JIT) 编译器是JVM运行时环境的一个重要组件,本文主要介绍了即时编译中的client,server以及分层编译器的原理以及优化手段。通过这些内容有助于对即时编译器加深理解,遇到相关问题时可以有效的分析和排查。在即时编译器中除了经典的client以及server编译器还有新一代的编译器:Graal编译器,它集成了即时编译器和提前编译器的功能感兴趣的可以自行查阅。参考《深入理解Java虚拟机》周志明《JAVA性能权威指南》Scott Oaks。

源码分析,技术沉淀 3539

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

8. 让java性能提升的JIT深度解剖

所以JVM其实跟语言是解耦的,也就是没有直接关联,JVM运行不是翻译Java文件,而是识别class文件,这个一般称之为字节码。还有像Groovy 、Kotlin、Scala等等语言,它们其实也是编译成字节码,所以它们也可以在JVM上面跑,这个就是JVM的跨语言特征。Java程序在运行的时候,主要就是执行字节码指令,一般这些指令会按照顺序解释执行,这种就是解释执行。本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。6、JIT优化技术之标量替换与栈上分配。

俺就是菜的很的博客 1999

Java 中的 JIT(Just - In - Time,即时编译)

综上,JIT 是 Java 虚拟机实现高效运行的核心技术之一,通过动态识别热点代码并即时编译成机器码,结合分层编译等策略,在运行时对 Java 程序进行深度优化,平衡了 Java 语言跨平台的便利性和高性能运行的需求,对 Java 程序在服务端、桌面端等各种场景下的广泛应用提供了重要的性能保障。(针对当前运行平台的 CPU 指令集 ),并将编译后的机器码缓存起来,后续再次执行这些代码时,直接使用编译好的机器码,从而避免重复进行字节码解释执行,大幅提高程序运行效率。),JVM 运行时通过。

weixin_43521001的博客 1392

luaJIT字节码写入介绍(一)

luaJIT字节码写入介绍 这里我将对于luaJIT的字节码写入文件进行剖析介绍,来保证对于luaJIT指令集的深入了解 我们针对luaJIT中的,lj_bcwrite.c文件来进行解析 从第一步开始,根据观察我们可以发现,写入的入口方法是: int lj_bcwrite(lua_State *L, GCproto *pt, lua_Writer writer, void *data,

Hello_YJQ的博客 2840

Java编译过程、JIT编译详解、类加载过程

Java程序员必知的Java编译流程、JIT编译

在路上 2257

Java面试八股之JIT的概念

它是Java虚拟机(JVM)的一个重要组成部分,负责将Java字节码(编译后的中间代码)在运行时转换为可以直接在硬件上执行的机器码。对于识别出的热点代码,JIT编译器将其字节码转换为对应平台的机器指令,并进行一系列优化,如方法内联、消除冗余操作、循环展开、寄存器分配、分支预测等,以提高代码执行速度。编译后的机器码会被存储在代码缓存(Code Cache)中,后续执行时直接使用已编译的机器码,避免重复编译。如果代码缓存空间不足,JVM可能会清理掉不常用的编译代码,或者对代码缓存进行动态调整。

u012151345的博客 1297

JVM系列第4讲:从源代码到机器码,发生了什么?

在上篇文章我们聊到,无论什么语言写的代码,其到最后都是通过机器码运行的,无一例外。那么对于 Java 语言来说,其从源代码到机器码,这中间到底发生了什么呢?这就是今天我们...

陈树义 861

Java内存学习-JIT(Just In Time) 即时编译器1

Java jit即时编译器概念 相关参数

MyGaoRen的博客 1476

Java面试必备:什么是Java 中的 JIT (Just-In-Time)?

Just-In-Time (JIT) 编译是 Java 虚拟机 (JVM) 中的一项关键技术,它能够在运行时将 Java 字节码动态编译成机器码,从而显著提高程序的执行效率。与传统的静态编译不同,JIT 编译发生在程序运行期间,因此得名"即时编译"。

qq_58299462的博客 839

Java应用-JIT启动预热解决方案及验证结果

主要介绍CRaC的适用应用场景以及相对于传统CDS、AOT、JIT相关启动加速方案的优势。

nanxiaotao的博客 2380
上一篇: Java黄金五年——1~5年一个Java程序员从入行到大牛的晋升之路
下一篇: SpringBoot 之Thymeleaf模板.
大G哥
博客等级 码龄9年 5303粉丝 1408原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值