Java代码有很多种不同的运行方式。比如说可以在开发工具中运行,可以双击执行jar文件运行,也可以在命令行中运行,甚至可以在网页。
这些执行方式都离不开JRE,Java运行时环境。
JRE仅包含运行Java程序的必需组件,包括Java虚拟机以及Java核心类库等。我们Java程序员经常接触到的JDK(Java开发工具包)同样包含了JRE,并且还附带了一系列开发、诊断工具。
然而,运行C++代码则无需额外的运行时。往往把这些代码直接编译成CPU所能理解的代码格式,即机器码。
比如下图的中间列,就是用C语言写的Helloworld程序的编译结果。
C程序编译而成的机器码就是一个个的字节,它们是给机器读的。那为让开发人员也能理解,用反汇编器将其转换成汇编代码(如下图的最右列所示)。
; 最左列是偏移;中间列是给机器读的机器码;最右列是给人读的汇编代码 0x00: 55 push rbp 0x01: 48 89 e5 mov rbp,rsp 0x04: 48 83 ec 10 sub rsp,0x10 0x08: 48 8d 3d 3b 00 00 00 lea rdi,[rip+0x3b] ; 加载"Hello, World!\n" 0x0f: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 0x16: b0 00 mov al,0x0 0x18: e8 0d 00 00 00 call 0x12 ; 调用printf方法 0x1d: 31 c9 xor ecx,ecx 0x1f: 89 45 f8 mov DWORD PTR [rbp-0x8],eax 0x22: 89 c8 mov eax,ecx 0x24: 48 83 c4 10 add rsp,0x10 0x28: 5d pop rbp 0x29: c3 ret
为什么Java要在虚拟机里运行?
Java作为一门高级程序语言,它的语法非常复杂,抽象程度也很高。因此,直接在硬件上运行这种复杂的程序并不现实。所以呢,在运行Java程序之前,需要对其进行一番转换。
转换怎么操作
设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换成该虚拟机所能识别的指令序列,即Java字节码。
之所以这么取名,是因为Java字节码指令的操作码(opcode)被固定为一个字节。
下图的中间列,正是用Java写的Helloworld程序编译而成的字节码。可以看到,它与C版本的编译结果一样,都是由一个个字节组成的。
同样可以将其反汇编为人类可读的代码格式(如下图的最右列所示)。
Java版本的编译结果相对精简一些,Java虚拟机相对于物理机而言,抽象程度更高。
/
# 最左列是偏移;中间列是给虚拟机读的机器码;最右列是给人读的代码 0x00: b2 00 02 getstatic java.lang.System.out 0x03: 12 03 ldc "Hello, World!" 0x05: b6 00 04 invokevirtual java.io.PrintStream.println 0x08: b1 return
Java虚拟机常见的是在各个现有平台(如Windows_x64、Linux_aarch64)上提供软件实现。一旦一个程序被转换成Java字节码,便可在不同平台上的虚拟机实现里运行,即“一次编写,到处运行”。
虚拟机的另外一个好处是它带来托管环境(Managed Runtime),代替我们处理一些代码中冗长而且容易出错的部分。自动内存管理与垃圾回收,这部分内容甚至催生了一波垃圾回收调优。
托管环境还提供了诸如数组越界、动态类型、安全权限等等的动态检测。
Java虚拟机具体是怎样运行Java字节码的?
以标准JDK中的HotSpot虚拟机为例,从虚拟机以及底层硬件两个角度,给你讲一讲Java虚拟机具体是怎么运行Java字节码的。
虚拟机视角,执行Java代码首先要将它编译而成的class文件加载到Java虚拟机。
加载后的Java类会被存放于方法区(Method Area)。实际运行时,虚拟机会执行方法区内的代码。
这和段式内存管理中的代码段类似。而且,Java虚拟机同样也在内存中划分出堆和栈来存储运行时数据。
但Java虚拟机会将栈细分为面向Java方法的Java方法栈,面向本地方法(用C++写的native方法)的本地方法栈,以及存放各个线程执行位置的PC寄存器。
运行过程中,每当调用进入一个Java方法,Java虚拟机会在当前线程的Java方法栈中生成一个栈帧,存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且Java虚拟机不要求栈帧在内存空间里连续分布。
当退出当前执行的方法时,不管是正常返回、异常返回,Java虚拟机均会弹出当前线程的当前栈帧,并舍弃。
硬件视角,Java字节码无法直接执行。因此,Java虚拟机需要将字节码翻译成机器码。
HotSpot翻译过程有两种形式:
- 解释执行,逐条将字节码翻译成机器码并执行
无需等待编译 - 即时编译(Just-In-Time compilation,JIT),将一个方法中包含的所有字节码编译成机器码后再执行
实际运行速度更快
HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点:
先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。