📚《深入浅出JVM字节码》
《Java虚拟机字节码:从入门到实战》的开源版本。作者通过自己的实战经验,整合出一套适合新手的高效学习教程。归纳并提炼知识点,制定合理路线,帮助读者更快掌握核心技术。
在汇编语言中,除直接内存操作的指令外,其它指令的执行都依赖寄存器,如跳转指令、循环指令、加减法指令等。汇编指令集是由硬件直接支持的,不同架构的CPU提供的汇编指令集也会不一样1。
以一个经典的++i面试题为例,使用c语言编写的实现如下。
int m = ++i;
反汇编后对应的32位x86 CPU的汇编指令如下。
inc dword ptr [ebp-44h]
mov eax,dword prt [ebp-44h]
mov dword ptr [ebp-4ch],eax
这三条指令的意思是,先将[ebp-44h]内存块存储的值加1,而dword ptr相当于c语言中的类型声明。接着将[ebp-44h]内存块存储的值放入到eax寄存器,最后将eax寄存器存储的值放到[ebp-4ch]内存块,也就是赋值给变量m。由于i和m是在栈上分配的内存,因此[ebp-44h]对应i的内存地址,[ebp-4ch]对应m的内存地址。
汇编指令不能直接操作将一块内存的值赋值给另一块内存,必须要通过寄存器。32位x86 CPU包括8个通用寄存器,EAX、EBX、ECX、EDX、ESP、EBP、ESI、EDI,其中EBP、ESP用做指针寄存器,存放堆栈内存储单元的偏移量。这些看不懂没关系,这也不是本书的重点。
上述++i的例子使用java代码实现如下。
public static void main(String[] args) {
int a = 10;
int result = ++a;
System.out.println(result);
}
使用javap命令输出这段代码的字节码如下。
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: istore_1
3: iinc 1, 1
6: iload_1
7: istore_2
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_2
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: return
字节码指令前面的编号我们暂时可以理解为行号。在本例中,行号0到7的字节码指令完成的工作是将变量a自增后的值赋值给result变量。下面将详细分析这几条指令的执行过程。
图1.10 bipush指令执行过程
图1.11 istore指令执行过程
图1.12 iinc指令执行过程
图1.13 iload_1指令执行过程
图1.14 istore_2指令执行过程
从++i的例子中,我们可以看出,字节码是依赖操作数栈工作的。在虚拟机上执行的字节码指令虽然最终也是编译为机器码执行,但编写字节码指令时并不需要我们考虑需要使用哪些寄存器的问题,这些都交由JVM去实现。
使用汇编指令编写代码,我们需要考虑CPU的架构,有多少个寄存器可选,了解硬件,需要关心每条指令操作多少个字节,在使用寄存器之前需要考虑是否需要备份寄存器的当前值,指令执行完之后是否需要恢复寄存器的值。而使用依赖栈工作的字节码指令编写代码,我们只需要关心每条字节码指令需要多少个参数,按顺序将参数push到操作数栈顶。如果指令执行完有返回值,在指令执行完成后,操作数栈顶存储的就是返回值。
发布于:2021 年 06 月 23 日
作者: 吴就业
链接: https://github.com/wujiuye/JVMByteCodeGitBook
来源: Github Pages 开源电子书《深入浅出JVM字节码》(《Java虚拟机字节码从入门到实战》的第二版),未经作者许可,禁止转载!
📚目录
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。