Java是怎么运行在电脑上的?
要回答这个问题,我们需要知道一个很简单,但是又经常被遗忘的道理:“电脑只能读懂机器码”。在这个条件下,我们就能知道,如果想要让电脑读懂人类的意思,就只需要把逻辑转换成一种机器能够读懂的指令就可以了。
那么Java是怎么实现的?这里就需要提及到JVM了。JVM 提供了一个能在各个环境下运行特定指令的环境,而编译器能够将Java的代码转换成JVM能够识别的指令序列(也可以称为Java字节码)。这样通过一个运行环境和特定指令,Java代码就能转换成计算机能够执行的指令。
运行过程
要描述Java代码的执行过程,可以用两个角度出发:JVM 角度和硬件角度。
JVM角度
Java代码编译层class 文件,再被加载到JVM中。加载之后的Java 类会被存放到方法区中,实际运行的时候JVM会直接执行方法区中的代码。
每当有方法被调用的时候,JVM 都会在当前的方法栈中生成一个栈帧,用来存放局部变量和字节码的操作数。当前方法退出操作,不管是正常还是异常退出,JVM都会弹出当前的栈帧,并舍弃掉。
需要注意的是栈帧的大小是事先定义好的,而且JVM 也不要求栈帧连续分布。
而JVM 的内存管理模型,也可以分为两种类型:线程共享和线程私有。线程共享部分有:方法区、堆内存;线程私有的内存部分为:PC寄存器、Java方法栈和本地方法栈。
硬件角度
从上边的信息我们可以知道,需要将Java的代码翻译成Java 字节码,JVM才能执行代码。而具体的计算器还需要JVM 把Java字节码编译成机器码才能执行。这个Java 字节码翻译成机器码的过程就会直接影响到代码执行的效率。在HotSpot的实现中,Java 字节码编译成机器码有两种方式:解释编译和即时编译。
解释编译,是逐条将字节码翻译成机器码,这样做的优点是无需等待一个漫长的编译过程。而即使编译是把所有的Java 字节码编译成机器码再进行执行,虽然编译的过程会长一些,但是实际运行速度会更快。
HotSpot中默认采用的是混合模式,即解释执行字节码,然后将其反复执行的热点代码,以方法为单位进行即时编译。
即时编译
HotSpot的即时编译器有3种:C1、C2和Graal。其中Graal是Java 10 之后才引入的,这个后续再聊。
C1,又称为client编译器,采用的优化手段比较简单,编译时间短;C2,采用的优化手段比较长,但是编译之后的代码执行效率比较高。
HotSpot即时编译,是通过分层编译实现的,即:热点数据显会被C1编译,热点中的热点会再被C2编译。同时,为了不干扰正常运行,HotSpot的即时编译是在额外的线程中进行,默认会以1:2的方式配置C1,C2编译器。
总结
想要在计算机上运行Java的代码,需要先将Java 代码编译成 Java字节码,再通过JVM 编译成机器码。
Java 代码编译成Java 字节码的之后,会按照JVM的内存模型存放到不同的区域,总体可以分为线程共享和线程私有两块区域。
为了提升运行效率,JVM翻译Java 字节码为机器码的过程中,会对热点数据进行优化,具体方式是通过即时编译。即时编译这个过程也是渐进的,先用C1编译,如果还是比较热点的数据,机会用C2进行编译。为了不干扰正常运行,即时编译是在额外的线程中进行的。