JVM执行引擎: 中间语言翻译

这篇日志把JVM中的中间语言转换总结一下,看看从中间语言转换成机器码的方式是怎样的。方法是程序最基本的单元,由指令的集合组成,在Java里就是字节码的组合,即对字节码的封装,JVM在进入一个Java方法后,将每一条字节码指令取出,逐条按顺序执行,例如基本四则运算,与或非,左移右移等,不过JVM的方法调用不像CPU执行原子指令那样,直接跳到对应的方法代码段,而是先把Java代码编译成中间语言-字节码,接着在程序运行过程中动态将字节码指令解释成机器指令,所以JVM最后执行的就是一堆机器指令, 这样做的目的是实现兼容性,在一个平台上完成的代码,在另一个平台上依旧可以解释运行。

兼容性的实现方式

拿C来举例,C实现兼容性的方式是在不同的操作系统上面使用不同的编译器来编译,例如在Windows上通常用mingw,Linux上用gcc编译,gdb来调试,Windows上加锁操作调用的API是CreateMutex(),Linux是pthread_mutex_lock();Windows上创建信号量是CreateSemaphore(),Linux上是sem_init()。C语言这种方式实现的兼容性,靠的是在不同的操作系统环境下开发不同的编译器,编译器能够将同样的一段C程序编译成当前平台、操作系统匹配的机器指令,程序员如果想要开发兼容Windows和Linux的程序,必须熟练两套不同的API,总的来说,“成本”太高。

中间语言转换

像Java,C#这类的高级语言编写的程序,都是先被编译成中间语言,再由虚拟机解释运行,程序运行过程中实时将字节码指令集翻译成机器指令,这样做的好处是,无论你在Windows,Linux还是其他平台上,编写的Java程序源代码都会被编译成同样的中间语言-字节码指令集,即通过中间语言的方式实现了兼容性,这样我们在编写程序时,哪怕涉及API的调用,也可以使用统一的高级语言编写,即Java语言规范和API调用,无论之后程序是部署在哪个平台上,编译,解释运行时都可以交由虚拟机来完成。将Java程序编译成字节码指令集后,CPU还是看不懂的,无法直接运行字节码,因为字节码本来作为中间语言,就是用来实现兼容性而已,不同平台编写的程序编译成相同的字节码指令集后,JVM还要将字节码指令集合解释成机器指令,这样CPU才能执行程序。

中间语言到机器码

JVM实现将字节码指令集合解释成机器码的方法,其中一种是用C语言将Java字节码解释成C程序,用C来对每一个字节码指令实现一个对应的函数,在解释执行字节码时,就能调用对应的C函数。例如看下面一个例子,用C语言来解释一个模拟的解释器:
在这里插入图片描述

解释器的思想就是通过指令来操作,例如编号0x01对应的是QuickSort,0x02对应的是Selection_Sort,以此类推,run()函数相当于虚拟机中的执行引擎,执行引擎或接收指令编码,通过指令编码来决定对数据执行什么操作。例如上面例子中,如果指令编码是0x01,则对待排序列进行快速排序,如果指令编码是0x02,则对待排序列进行选择排序,等等等等,可以说是最简单的JVM执行引擎模型。这是通过C程序将中间语言翻译到对应的机器码,还有一种是直接将中间语言翻译成机器指令,执行效率会更高,这种方式需要操作CS:IP段寄存器,将其直接指向代码的首地址,

直接到机器码

CS:IP段寄存器

CS和IP是CPU里面的两个寄存器,CS寄存器保存的是段地址,IP寄存器保存的是偏移地址,通过这两个寄存器的值就可以确定内存中的一个地址,CS:IP寄存器确定了当前CPU将要读取执行的指令,通过这两个寄存器,定位到需要执行的函数所在内存的首地址,然后从该地址中取出指令来执行,JVM让CPU直接执行字节码对应的机器码,方法就是修改CS:IP段寄存器。

C语言语法糖

举个例子,使用C语言提供的语法糖,通过CS:IP段寄存器直接指向某一个函数的首地址,方式是使用函数指针,函数指针就是一个指针变量,存放(或者你们常说的指向)一个函数的地址:
在这里插入图片描述

可以看到,第22行定义了一个函数指针,即该指针arrSort存放了一个函数的入口地址,下一行即让函数指针指向run()函数的入口,这样当代码编译后,arrSort指针就直接存放了其机器码指令,运行到第26行时,就会跳转到run()函数里面,和前面一样,根据编号来执行不同的排序方法,实现思想相当于arrSort指向了一串机器指令,根据不同的指令执行不同的方法,实现了动态执行机器码。

有了上面的思路,JVM在实现从中间语言直接翻译到机器码的方式,就是预先定义好一组本地机器指令,例如:
const unsigned charcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f}​;

然后通过C语言提供的语法糖,将CS:IP段寄存器指向这一组指令,达到直接执行机器码的目的。中间语言,也就是字节码,JVM对其有自己的代码执行方式,虽然使用中间语言写的代码量可能不多,但翻译到实际机器码后,指令数量会增加很多,理论上执行效率也会收到影响。

即时编译-Hot Spot

因为字节码不能被CPU直接执行,所以需要JVM根据自己的翻译规则,将字节码指令翻译成可被CPU执行的机器指令,虽然使用中间语言写的代码量可能不多,但翻译到实际机器码后,通常一条字节码指令都会被翻译成一堆机器指令,JVM对其有自己的代码执行方式,因为生成了大量的机器指令,理论上执行效率会收到影响。

为了提升性能,JVM选择用JIT-Hot Spot,即“just-in-time compilation”热点即时编译的方式,当一段代码第一次被执行时,并不对立刻对其进行编译,通常是解释执行,原因是,如果这段代码只执行一次,在未来再次被执行的次数很少甚至没有,那么将其编译成机器码会浪费资源,所以选择用解释器将代码翻译成字节码来解释执行。如果在解释执行过程中,JVM发现一段代码将来会被多次执行,例如一个方法被多次调用,或者是一个循环,那么JVM就会选择对其进行编译执行, 因为编译成机器指令后,执行效率会高很多,这就是JVM对上面问题的优化,如果字节码选择全部编译,那么将会出现大量的机器指令要执行,指令数量巨大,理论上效率就没那么高.

JIT即时编译使用热点编译方式,一种既有解释执行又有编译运行方式结合,就是一种优化,对于执行频率较高的代码,选择将字节码编译成机器指令来执行,提高性能,总的来说,解释器解释运行,节约内存,编译器编译运行,提高性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值