虚拟机栈是程序方法执行的地方。
虚拟机栈中每一个栈帧可以理解为一个方法。每个栈帧中包涵执行方法所需要的操作数栈、局部变量表、动态链接和返回地址。
局部变量表
用来存放方法运行时的传入的参数和方法内部的局部变量,主要是存放Java的8大基础类型,如果是对象的话,则存放对象的引用地址,实际对象会在堆上分配内存(大部分情况下,有少数情况也会有栈上分配的情况)。局部变量的大小最大容量会编译后记录在class文件中。
操作数栈
用来存放和当前操作相关的数据。
动态链接
当方法内调用其他方法时,因为Java语言的多态特性,需要依靠动态链接找到实际需要执行的方法。即将符号引用转换为直接引用。
返回地址
正常返回:
调用程序计数器中的地址作为返回
异常返回:
返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息
怎么看字节码指令呢?
下面是笔者常用的几种方法
- javap命令,操作比较麻烦一点,但还是很好用的
- 插件:ASM Bytecode Viewer,安装完成后在文件中左键,选择功能即可
- Kotlin代码,Tools > Kotlin > Show Kotlin Bytecode
方法2、3都是在IDEA/AS开发工具中用到的。最近Kotlin用的比较多,所以常用第三种。
源码:
fun add():Int {
val value1 = 1
val value2 = 7
val value3 = value1+value2
return value3 + 3
}
Kotlin Bytecode处理后:
// ================com/chenlong/KtTestKt.class =================
// class version 50.0 (50)
// access flags 0x31
public final class com/chenlong/KtTestKt {
// access flags 0x19
public final static add()I
L0
LINENUMBER 4 L0
ICONST_1
ISTORE 0
L1
LINENUMBER 5 L1
BIPUSH 7
ISTORE 1
L2
LINENUMBER 7 L2
ILOAD 0
ILOAD 1
IADD
ISTORE 2
L3
LINENUMBER 9 L3
ILOAD 2
ICONST_3
IADD
IRETURN
L4
LOCALVARIABLE value3 I L3 L4 2
LOCALVARIABLE value2 I L2 L4 1
LOCALVARIABLE value1 I L1 L4 0
MAXSTACK = 2
MAXLOCALS = 3
@Lkotlin/Metadata;(mv={1, 4, 0}, bv={1, 0, 3}, k=2, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0008\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u00a8\u0006\u0002"}, d2={"add", "", "d1023"})
// compiled from: KtTest.kt
}
现在开始读读读读读
LX这个可以不用管
LINENUMBER X X也可以不用(LINENUMBER 4,这个的4其实.java文件的行号,不看也可以)
下面逐行阅读:
-
初始情况
JVM虚拟机栈操作0.png
-
ICONST_1:把常量 1 压入操作数栈栈顶
JVM虚拟机栈操作1.png -
ISTORE 0:把操作数栈顶的元素出栈并放入局部变量表下标 0的位置
JVM虚拟机栈操作2.png -
BIPUSH 7:把常量7压入操作数栈栈顶
为什么7是BIPUSH而1是ICONST呢?
这应该是JVM的优化吧,具体我也不懂。
-128到127之间,-1到5是ICONST,其他是BIPUSH,-128以下和127以上用SIPUSH。
我是怎么知道的?
Kotlin Bytecode很强大,实时翻译,稍微测一下就知道了!
JVM虚拟机栈操作3.png -
ISTORE 1:把操作数栈顶的元素出栈并放入局部变量表下标 1的位置
JVM虚拟机栈操作4.png -
ILOAD 0:把局部变量下标 0的值放入操作数栈栈顶
JVM虚拟机栈操作5.png -
ILOAD 1:把局部变量下标 1的值放入操作数栈栈顶
JVM虚拟机栈操作7.png -
IADD:将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶
JVM虚拟机栈操作8.png -
ISTORE 2:把操作数栈顶的元素出栈并放入局部变量表下标 2的位置
JVM虚拟机栈操作9.png -
ILOAD 2:把局部变量下标 2的值放入操作数栈栈顶
JVM虚拟机栈操作10.png -
ICONST_3:把常量 3压入操作数栈栈顶
JVM虚拟机栈操作11.png -
IADD:将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶
JVM虚拟机栈操作12.png IRETURN:把操作数栈里面的11返回。然后整个栈帧从虚拟机栈中出栈,局部变量表和操作数栈完成任务,拉出去销毁。
从这里可以看到基于栈实现的JVM,运行起来还是蛮复杂的。