1.前言
在《【编译引擎】学习阅读Class文件结构(javap版)》中,我们看到了Code属性包含了JVM指令序列。
在理解了Class文件结构之后,学习JVM指令有助于我们更进一步地理解Class文件结构。
然而,JVM指令是JVM规范另一个大篇幅知识点,我们应该如何高效学习它呢?
本文将通过上述代码,阐述JVM指令的执行过程(例如:局部变量表中的数据入栈、栈上数据存储回局部变量表等),为读者展示JVM指令的全貌以及学习方法。
2.JVM指令概览
2.1.本质-信息压缩
在学习具体的JVM之前,我们应该先来看看JVM指令的本质以及它存在的意义。
不知道您是否考虑过这样一个问题:“如何表达一个方法的实现呢?”
您可能会说:可以用代码来描述,如下伪码所示:
|
|
那么,我们把问题增加一些限制:“如何用最小的信息量表达一个方法的实现?”
聪明的读者可能会想到"代号法”,即为上述伪码取个特殊的代号(假设"int i = 1"的代号是"帅哥”,“print(i)“的代号是"美女”),那么上述伪码可以简化为:
|
|
我们继续把问题增加一些限制:“如何用计算机能理解的最小信息量表达一个方法的实现?”
什么是计算机能理解的信息?显然是字节。所以,我们可以约定"0A"就是"帅哥”,“0B"就是"美女”,如下:
|
|
同样的信息,在某些场景下(对于人)越大越好,在某些场景下(对于机器)越小越好。
更关键的是,二者能相互转换——这就好像三体中智子的高维折叠和低维展开。
这或许就是计算机的世界中,最令人着迷的地方。
2.2.JVM指令格式
JVM指令的格式一般是这样的:
操作码+操作数
操作码为1个字节,在JVM规范中约定了这个字节表达的具体的JVM指令含义。
这是一个有趣的细节:1个字节是8位,所以JVM指令的操作码最多只有255个
操作数可以没有,也可能有多个。
2.3.JVM指令分类
JVM规范中的JVM指令非常多,我们可以根据指令的使用场景分类,本文仅列举实战中最常用的场景:
- 加载与存储:比如将局部变量表的数据放到虚拟机栈中
- 算数指令:比如i++
- 类型转换指令:比如int转double
- 对象创建/字段访问:比如创建一个类的实例,并且访问该实例的某个属性
- 方法调用:比如访问一个类的静态方法
- 异常处理:比如抛出一个异常
- ……
2.4.真理都在规范中
当我们具备了前面的基本知识,最高效、最准确地学习JVM指令,就要查阅JVM规范了:
3.实例解读
为了方便阅读,我们将javap输出的日志简化后展示如下:
|
|
3.1对象创建指令
new是操作码,#7是操作数。
#7表示常量池中的常量,从javap的输出可知,#7表示IllegalArgumentException异常。
new表示创建了IllegalArgumentException异常对象。
创建了异常对象后,对象本身放到堆中,对象的内存地址会放到栈中。
下一步就是dup,dup只有操作码,没有操作数。
dup就是将栈中前一个元素进行了复制。
那么为什么要dup呢?
这就涉及到后面方法调用的指令了,当执行了invokespecial
指令,就会将栈中的#0001
出栈。
所以,一般创建了对象后,会紧跟着dup一下。
3.2.常量入栈指令
ldc是操作码,#8是操作数。
根据javap的输出提示,#8表示的是字符串类型常量"exception”。
ldc负责将这个常量入栈:
3.3.方法调用指令
invokespecial是操作码,#9是操作数
#9表示IllegalArgumentException
异常的构造函数
invokespecial表示了调用特定的函数,在这里调用的是IllegalArgumentException
异常的构造函数,并且需要给构造函数输入一个字符串参数"exception”。
此时运行时数据区变成了这样:
3.4.异常处理指令
athrow只有操作码,表示抛出IllegalArgumentException
异常,并结束了test方法。
至此,我们通过test()方法,就覆盖了JVM指令主要的4个场景:创建对象->常量入栈->调用方法->抛出异常。
3.5.main函数的JVM指令执行过程
有了3.1~3.4的基础,我们再看main函数的执行就很容易举一反三了:
getstatic #2
属于类/对象的属性获取场景,即将System.out放到栈中
invokevirtual #4
类似invokestatic指令,也是调用方法,即调用System.out.println
此时,控制台打印了"start”。
执行到invokestatic #5,即执行了test()方法,会抛出异常,导致main函数不再往下执行后面的JVM指令。
因此,最终控制台打印了start后抛出IllegalArgumentException
异常
4.总结
本文接着javap案例,进一步解读了javap的JVM指令部分,具体如下:
- JVM指令的本质是信息压缩
- JVM指令格式一般包含1个操作码+N个操作数
- 可以根据场景对JVM指令分类,有助于高效学习JVM指令
- 通过一个示例代码,解读了JVM指令的运行过程
5.参考资料
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
《深入理解Java虚拟机:JVM高级特性与最佳实践》