读懂Java字节码

Java代码的运行过程

Java字节码

栈帧的概念

栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素。每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。

每一个栈帧在编译程序代码的时候所需要多大的局部变量表,多深的操作数栈都已经决定了 一次一个栈帧需要多少内存,不会受到程序运行期变量数据的影响,仅仅取决于具体的虚拟机实现。

一个线程中方法调用可能很长,很多方法都处于执行状态。

Java字节码栈帧的结构

从上面的图可以看出,栈帧的组成主要分为4个部分:

局部变量表

顾名思义,用来存储局部变量和方法的内存空间,第一位是当前类的指针this,后面的是当前函数的参数和方法内的变量。比如一个函数有两个参数x,y,第一位是this指针,第二个是x,第三个是y
局部变量占用的空间大小在Java程序被编译成Class文件时就已经确定了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
问:是如何在编译器就能够确定方法局部变量表的大小?

```

#### 操作数栈

操作数栈和局部变量表类似,也是用来存储数据变量,但是操作数栈不是通过索引访问变量,而是通过出栈和压栈操作来对数据进行访问。
#### 动态链接

在Class文件的常量池中存有大量的 符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化 称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。

### 一个例子

随便写一个例子,尽量多的包含函数中的运算方法,基本的代码如下:

``` Java

public class ClassDemo {
public static void main(String[] args) {
ClassDemo classDemo = new ClassDemo();
classDemo.run(5);
}
public void run(int i) {
int j = 1;
int s = 2;
int d = 3;
int sum = add(i, j);
double result = sum * s / d;
for (int k = 0; k < 3; k++) {
result++;
}
try {
Thread.sleep(100);
}
catch (Exception ex){

}
if (result > 1) {
result--;
}

System.out.println(result);
}

public int add(int x, int y) {
return x + y;
}
}

```


使用`Javac`命令编译成`.class`文件,打开class文件内容如下:

``` content
cafe babe 0000 0034 0031 0a00 0c00 1c07
001d 0a00 0200 1c0a 0002 001e 0a00 0200
1f05 0000 0000 0000 0064 0a00 2000 2107
0022 0900 2300 240a 0025 0026 0700 2701
......

文件中的为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栈帧的结构局部变量结构如下:

Java字节码栈帧的结构