本文将通过是实例简单介绍JVM字节码基于的执行过程

Stack Frame

  1. 一个方法从调用到执行完成,对应着一个栈帧(Stack Frame)在虚拟机栈(VM Stack)里面从入栈到出栈的过程
  2. 编译字节码期间,栈帧需要多大的局部变量表(Local Variable Table),多深的操作数栈(Operand Stack)都已经完全确定,并且写入到方法表的Code属性中
  3. 活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧关联的方法称为当前方法
  4. 局部变量表
    • 用于存放方法参数方法内部定义的局部变量
    • 方法表中的locals属性记录了局部变量表的最大容量
    • 局部变量表的存储单元为32-bit的slotbooleanbytecharshortintfloat等会占用一个slotlongdouble会占用两个slot
    • 如果执行的是实例方法,那么第0个slot默认存储this
    • 为了节省栈帧空间slot是可重用
  5. 操作数栈
    • 方法表中的stack属性记录了操作数栈的最大深度
    • 操作数栈可以容纳任意的Java数据类型,32-bit数据类型所占用的栈容量为164-bit数据类型所以占用的栈容量为2

代码

1
2
3
4
5
6
7
8
9
package me.zhongmingmao.test;
public class BytecodeExecution {
public int calc() {
int a = 100;
int b = 200;
int c = 300;
return (a + b) * c;
}
}

字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# javap -v
public int calc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: bipush 100
2: istore_1
3: sipush 200
6: istore_2
7: sipush 300
10: istore_3
11: iload_1
12: iload_2
13: iadd
14: iload_3
15: imul
16: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 this Lme/zhongmingmao/test/BytecodeExecution;
3 14 1 a I
7 10 2 b I
11 6 3 c I
  1. stack=2,这个在分析下面16个JVM字节码指令的运行过程后就能验证这是正确的,从Java源代码是无法直观的得出这个值
  2. locals=4,从Java源代码可以看出,calc()只定义了abc三个局部变量,而calc()又是实例方法,局部变量表第1个slot默认会有记录this,因此能得出locals=4(在这里slot没有进行复用)
  3. JVM字节码中的LocalVariableTable属性,很清晰表述了局部变量表的布局,其中需要注意的是Start指的是局部变量生命周期开始的字节码偏移量Length指的是作用范围覆盖的长度,而并非变量本身的长度
  4. args_size=1实例方法默认会传入this,因此args_size=1

执行过程

bipush 100

istore_1

iload_1

iload_2

iadd

iload_3

imul

ireturn