使用以下代码为例,来查看它的字节码文件。这里使用了idea的jclasslib Bytecode Viewer插件更直观的查看字节码。
public class Test {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.add(2L,3));
}
private long add(long a,int b) {
return (a+b);
}
}
首先来main方法开始执行,虚拟机栈中main方法的栈帧入栈。
再看到main方法的字节码和局部变量表
1、new #2:#2为常量池中的引用,该常量又引用了#33,最终为一个字符串常量,包名/类名。
new指令的作用,可以从官网查看到https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.new(后续其他指令就不截图了,可以前去官网查看),在堆上创建对象并将对象的引用压入当前栈帧的操作数栈中。实际上new指令还有另外两个指令indexbyte1,indexbyte2,所以new指令占用了3行(0,1,2),dup指令只能从3开始。
2、dup:该指令会复制操作数栈栈顶的值,然后将该值入栈。
3、invokespecial #3 :#3为Test类的init方法,(就是自动生成的无参构造方法)。调用该方法,弹出操作数栈的objectRef,创建init方法的栈帧,将对象引用赋值给init局部变量表序号0的this对象。
init方法的局部变量表
看到init方法的字节码:实际上啥都没干,如果我们自己写构造方法添加逻辑,这里就会有我们自己的逻辑对应的字节码。(这里啥都没干,指的是我们没有写的业务代码,实际上init会对类的非静态属性和非静态语句块进行初始化)
aload_0:加载局部变量表位置为0的值,到操作数栈。
invokespecial #1 :然后调用父类object类的init方法。
return:丢弃当前操作数栈的所有数据,将控制权返还给调用方,恢复调用方的栈桢。
此时init方法执行完毕。
看回main方法字节码的执行流程
4、astore_1:将操作数栈栈顶的数据弹出,存入到局部变量表的序号为1的位置,此时main方法中的test局部变量就有了值。此时操作数栈啥都没有了。
5、getstatic #4 <java/lang/System.out> : 获取到Sysytem的静态对象out,并且压入操作数栈。
6、aload_1:加载局部变量表位置为1的值压入操作数栈
7、ldc2_w #5 <2> :从常量池加载long或者double类型(64位)的数据压入操作数,由于2被声明为long类型,在常量池表中占据两个位置。
8、iconst_3:将int类型的数据(值为3)压入操作数栈
9、invokespecial #7 <jvm/Test.add>:调用Test的add方法。
首先看到add方法的局部变量表
然后将3,2,objectRef弹出。创建add方法的栈帧,压入当前虚拟机栈中。弹出的值赋值到add方法的栈帧的局部变量表,this对象(0号位置)为objectRef的值,2为1号位置的值,3为3号位置的值。由于a对象是long类型,占用2个位置
此时看到add方法的字节码
10、lload_1:加载局部变量表位置1的long类型的数据压入操作数栈。(2)操作数栈中的值
iload_1:加载局部变量表位置3的int类型的数据压入操作数栈。(3,2)
i2l:将操作数栈顶的int数据弹出,转化为long类型的数据,再压入栈中。(我们栈顶的int类型的3被转化为了long类型的3) (3,2)
ladd:弹出栈顶两个long类型的数据,进行相加,相加的结果再压入栈中。(5)
lreturn:将操作数栈栈顶的long类型的数据弹出,压入调用方(main方法)栈桢的操作数栈中,丢弃当前操作数栈的所有数据,将控制权返还给调用方,恢复调用方的栈桢
此时add方法执行完毕
看回main方法的执行
11、 invokevirtual #8 <java/io/PrintStream.println>:调用println方法(println方法就不再画图了和看了),将操作数栈的参数和对象引用弹出,创建println方法的栈帧,然后局部变量表存入对象引用和参数值,println方法没有返回值,最后执行return指令返回。
12、return。main方法执行完毕。