致敬“Java之父”—詹姆斯·高斯林
第一次研究JVM所带入的问题:
何为Java NIO?
何为VMA?
何为内核映射?
发展:
1)机器码难以阅读、理解和排错,诞生了助记符,然后用助记符号来进行编程,编完以后大脑执行一遍以后确保没问题以后再将助记符转换成机器码,制作成孔带
2)二战还没结束期间,时势造英雄。Grace Hopper作为一位大牛,仍然觉得这样很麻烦,于是就开发了A-0 System,其能够自动将助记符转换成机器码。
3)当时的助记符就是所谓的汇编
机器生汇编,汇编生B,B生C,C生万物
一生二,二生三,三生万物
编程语言兼容底层系统的方式有两种:
1、通过编译器实现兼容
C、C++是通过特定的编译器编译成相应的平台的机器指令,但是编译器实现兼容时,如果涉及到系统的调用,往往都需要需改程序,调用特定系统的API,否则迁移到新平台之后无法运行。
2、通过中间语言实现兼容
Java、C#等语言,都属于这种兼容方式。
Java/C#文件先被编译生成中间语言(ML),中间语言指令由虚拟机进行解析和运行。无论身于哪种平台,生成的中间语指令都是相同,中间语的兼容性是由虚拟机负责完成的。
如今的Java性能相当的高,甚至比C/C++程序性能还要高。这是因为Java虚拟机内部对寄存器进行了大量手工优化,在某些场景下,人工优化自然会比C/C++编译器所做的机器优化效果要好很多。
A、中间语言翻译
CPU看不懂中间语言,无法直接执行中间语言,虚拟机必须将中间语解析翻译成对应的机器上的机器指令。
引出的课题:怎样将中间语言翻译成对应的机器指令并且得以执行。
课题中的两个问题:
1)怎么把中间语言翻译成对应的机器指令
2)翻译完后怎么执行程序?
科普君:Java的中间语—-字节码指令
1、字节码指令翻译到对应的机器码的一种可行办法:
通过C程序,将字节码的每一条指令都逐行解释成C程序。当执行字节码的程序——JVM程序本身被编译后,字节码指令所对应的C程序被一起编译成本地机器码,于是虚拟机在解释字节码指令时,自然就会执行对应的C程序所对应的本地机器码。意思就是,JVM已经被编译成机器码,字节码被解释成C语言后马上也被编译成机器码,就是大家现在都是机器码了,JVM在解释字节码的时候顺便也执行对应C程序对应的本地机器码。
2、通过C程序进行翻译
栗子,发明某种中间语,实现两个正整数相加,这条指令的助记符为iadd,对应的数字唯一编号是0x01
//虚拟解释器
int add(int a, int b, int code){
if(code == 0x01){
return a+b;
}
return -1;
}
add()看作为执行引擎,执行引擎接收操作数和指令编码,判断指令编码是否iadd,如果是iadd指令便对两个入栈的操作参数执行加法运算,并返回结果。(第一代JVM的执行引擎)
3、直接翻译为机器码
CPU执行一段代码只需要将CS:IP段寄存器指向代码段的入口处即可。
CS与IP是物理CPU内部的两个寄存器,也是一台物理机器最重要的寄存器,因为CPU在取指令时是完全依靠这两个寄存器的。CS寄存器保存段地址,IP保存偏移地址。
CS和IP这两个寄存器的值能够唯一确定内存中的一个地址。
函数跳转的本质就是修改CS和IP着两个寄存器的内容。
修改CS:IP段寄存器可以使用汇编直接修改,也可以在高级语言中通过语法糖(语法规则)的形式修改。
语法规则只存在于语言中,到了机器码层面就已经没有语法规则了,只有0和1。
使用C程序提供的语法糖修改CS:IP段寄存器的指向:
#include <stdio.h>
int run(int a, int b);
int main(int argc, char* argv[])
{
int a = 10;
int b = 11;
int (*func)(int,int);//定义函数指针
func = *run;//初始化函数指针,使其指向int run(int,int)函数入口
int r = func(a, b);//执行函数
printf("r=%d\n",r);
return 0;
}
int run(int a,int b) {
return a + b;
}