1. 编程语言兼容性
JVM最初的目标:
开发者无须关注底层平台就能实现兼容性。
实现编程语言兼容性大体分为两种方式:
-
通过编译器实现兼容
例如:C、C++等
实现原理:
针对不同的平台开发不同的编译器,编译器能将同样的代码段翻译成与目标平台匹配的机器指令。
缺陷:
如果涉及系统调用,大多都需要修改程序,调用特定的API。
-
通过中间语言实现兼容
例如:java、C#等
实现原理:
程序被编译后生成中间语言(ML),中间语言指令由虚拟机负责解释和执行。
源代码编译生成的中间语言是相同的,兼容性由虚拟机提供,虚拟机在运行期将中间语言实时翻译成与特定平台匹配的机器指令并执行。
缺陷:
性能较低。
2. 中间语言翻译
既然jvm采用中间语言实现兼容,接下来要讨论中间语言如何翻译为机器码。
2.1 通过C程序翻译
最早期的JVM采用这种方式翻译中间语言。
实现原理:
使用C程序编写一个中间语言解析程序,每一个中间语言指令都有对应的函数实现,通过该解析程序来解析中间语言,然后执行对应的函数进行运算。
缺陷:
解析步骤导致性能低下。
2.2 直接翻译为机器码
首先要明白CPU执行程序原理:
要让CPU执行一段代码,只需要将CS:IP段寄存器指向到代码段入口即可。
CS寄存器:保存段地址
IP寄存器:保存偏地址
CS和IP这两个寄存器的值能够唯一确定内存中的一个地址,CPU在执行机器指令前通过这两个寄存器定位到目标内存位置,并将该位置处的机器指令取出来进行运算。
实现原理:
JVM提供了一种机制将中间语言(字节码)直接编译为本地机器指令,修改CS:IP段寄存器使其指向中间语言翻译成的机器码直接执行。
ps:修改CS:IP的方法有很多,可以使用汇编修改,C语言的函数指针(高级语言中的语法糖)等等。
缺陷:
中间语言有自己的一套内存管理和代码执行方式,因为中间语言本身不能被CPU执行,为了能够被执行,需要准备更多便于自我管理的上下文环境,最后才能执行目标机器指令。
因此,中间代码翻译成的机器码比自己编写机器码多出很多指令,即使与C相比也在指令数量上也繁杂了很多,指令数量的增多意味着执行的时间成本增加。
一句话概括:
中间语言翻译成了更多的机器码,导致执行效率降低。
3. 为什么不用JAVA做中间语言
JVM实现了把统计接口翻译成对应机器的指令这个功能,为什么需要生成中间语言(字节码),而不直接使用JAVA作为中间语言进行翻译呢?
java作为面向对象的语言,更具有人类主观性的特点,但是机器只认得内存、堆栈...,如果要将JAVA在运行时直接翻译为机器码的话,将会耗费更多的性能,导致执行效率降低。
所以出现了编译器,将java编译为只知道压栈、读写局部变量表等的中间语言(字节码)。再将字节码作为中间语言交给JVM执行,降低了JVM解释执行的难度,从而提升效率。
4. JVM指令
-
数据交换指令
JVM内存分为操作数栈、局部变量表、java堆、常量池、方法区。数据交换指令负责数据在这些内存区域之间的传送和交换。
JVM执行逻辑运算的区域是操作数栈,硬件执行逻辑运算的区域是寄存器。
-
函数调用指令
java的函数类型比较丰富,因此需要支持更多的函数调用方式。
JVM没有物理寄存器,所以用操作数栈和PC寄存器来替代。
java函数的代码没有被存放到代码段中,而是被放在一个code缓存中,每一个java函数的代码块在这个缓存中都会有一个索引位置,最终JVM跳转到这个索引位置处执行java函数调用。同时java函数又一定是被封装在类中的,因此JVM在执行函数调用时,还需要通过类寻址等一系列运算,不能像CPU硬件一样直接跳转就能找到对应的代码段。
-
运算指令集
JVM运算相关的指令集有算术运算、位运算、比较运算、逻辑运算等,JVM还为各种基本类型的运算提供不同的操作码。
-
控制转移指令
用于支持循环、条件判断、return返回等操作。
-
对象创建与类型转换指令