读懂Java字节码
Java代码的运行过程
栈帧的概念
栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素。每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。
每一个栈帧在编译程序代码的时候所需要多大的局部变量表,多深的操作数栈都已经决定了 一次一个栈帧需要多少内存,不会受到程序运行期变量数据的影响,仅仅取决于具体的虚拟机实现。
一个线程中方法调用可能很长,很多方法都处于执行状态。
从上面的图可以看出,栈帧的组成主要分为4个部分:
局部变量表
顾名思义,用来存储局部变量和方法的内存空间,第一位是当前类的指针this
,后面的是当前函数的参数和方法内的变量。比如一个函数有两个参数x,y
,第一位是this
指针,第二个是x
,第三个是y
。
局部变量占用的空间大小在Java程序被编译成Class文件时就已经确定了。
1 | 问:是如何在编译器就能够确定方法局部变量表的大小? |
文件中的为16进制代码, 文件开头的4个字节称之为 魔数,唯有以cafe babe
开头的class文件方可被虚拟机所接受,这4个字节就是字节码文件的身份识别。
0000
是编译器jdk版本的次版本号0,0034转化为十进制是52,是主版本号。
Java的版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一。
也就是说,编译生成该class文件的jdk版本为1.8.0。
再使用Javap -c ClassDemo.class >>code.txt
命令,反编译代码到code.txt中,反编译后的代码如下,run方法的执行已经写的有注释,不再赘述.
Compiled from "ClassDemo.Java"
public class ClassDemo {
public ClassDemo();
Code:
0: aload_0 //将第一个引用类型本地变量,this指针
1: invokespecial #1 //调用超类构造方法,实例初始化方法,私有方法 // Method Java/lang/Object."<init>":()V
4: return //void函数返回
public static void main(Java.lang.String[]);
Code:
0: new #2 //创建新的对象实例 // class ClassDemo
3: dup //复制栈顶一个字长的数据,将复制后的数据压栈
4: invokespecial #3 //调用超类构造方法,实例初始化方法,私有方法 // Method "<init>":()V
7: astore_1 //将栈顶引用类型值保存到局部变量1中
8: aload_1 //从局部变量1中装载引用类型值入栈 classdemo实例
9: iconst_5 //5(int)值入栈
10: invokevirtual #4 //运行时方法绑定调用方法 // Method run:(I)V
13: return
public void run(int);
Code: //第一个局部变量是this指针,第二个是形参i j是第三个变量
0: iconst_1 //1(int)值入栈。
1: istore_2 //将栈顶int类型值保存到局部变量2中。
2: iconst_2 //2(int)值入栈
3: istore_3 //将栈顶int类型值保存到局部变量3中。
4: iconst_3 //3(int)值入栈
5: istore 4 //d=3
7: aload_0 //将第一个引用类型本地变量 this指针
8: iload_1 //从局部变量1中装载int类型值入栈 i
9: iload_2 //从局部变量2中装载int类型值入栈 j
10: invokevirtual #5 //调用add方法 // Method add:(II)I
13: istore 5 //把函数的返回值,存到第五个变量sum中。
15: iload 5 //把第五个变量加载出来
17: iload_3 //iload_3读出s变量
18: imul //将栈顶两int类型数相乘,结果入栈 s*d
19: iload 4 //读取s变量
21: idiv //将栈顶两int类型数相除,结果入栈
22: i2d //将栈顶int类型值转换为double类型值
23: dstore 6 //把doubLe值存入第六个变量中
25: iconst_0
26: istore 8
28: iload 8
30: iconst_3
31: if_icmpge 46 //若栈顶两int类型值前大于等于后则跳转46
34: dload 6
36: dconst_1
37: dadd //将栈顶两double类型数相加,结果入栈
38: dstore 6
40: iinc 8, 1 //将整数值constbyte加到indexbyte指定的int类型的局部变量中,把第8个局部变量加1
43: goto 28 //跳转到28行
46: ldc2_w #6 //长整型100入栈 // long 100l
49: invokestatic #8 //调用静态方法 // Method Java/lang/Thread.sleep:(J)V
52: goto 57 //goto 跳转到57行
55: astore 8 //将栈顶引用类型值保存到局部变量8中
57: dload 6 //从局部变量6中装载double类型值入栈
59: dconst_1
60: dcmpl //比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈
61: ifle 70 //若栈顶int类型值小于等于0则跳转70行
64: dload 6 //加载第6个变量
66: dconst_1
67: dsub //将栈顶两double类型数相减,结果入栈
68: dstore 6
70: getstatic #10 //获取静态字段的值 // Field Java/lang/System.out:LJava/io/PrintStream;
73: dload 6 //加载第6个变量的值
75: invokevirtual #11 // 调用实例方法 print // Method Java/io/PrintStream.println:(D)V
78: return
Exception table:
from to target type
46 52 55 Class Java/lang/Exception
public int add(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
}
run栈帧的结构局部变量结构如下: