Java方法的字节码结构
以如下代码为例,对 Java 方法的字节码结构进行阐述(jdk 版本 1.8.0_301)。
package com.zero.demo;
public class MethodAnalyzeDemo {
public int doSomething(int i, int j) {
int result = i + j;
return result;
}
}
通过 javac 命令,可以对 .java 文件进行编译,将源代码编译成字节码,即生成 .class 文件。
javac MethodAnalyzeDemo.java
通过 javap -verbose 命令,将会根据 .class 文件的字节码,打印出 Java 类中方法的字节码信息。
PS C:\Users\Zero\Desktop\demo> javap -verbose .\MethodAnalyzeDemo.class
Classfile /C:/Users/Zero/Desktop/demo/MethodAnalyzeDemo.class
Last modified 2021-12-26; size 292 bytes
MD5 checksum ee74d225ac594d1577a0005f2106072d
Compiled from "MethodAnalyzeDemo.java"
public class com.zero.demo.MethodAnalyzeDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."<init>":()V
#2 = Class #13 // com/zero/demo/MethodAnalyzeDemo
#3 = Class #14 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 doSomething
#9 = Utf8 (II)I
#10 = Utf8 SourceFile
#11 = Utf8 MethodAnalyzeDemo.java
#12 = NameAndType #4:#5 // "<init>":()V
#13 = Utf8 com/zero/demo/MethodAnalyzeDemo
#14 = Utf8 java/lang/Object
{
public com.zero.demo.MethodAnalyzeDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public int doSomething(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=3
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: iload_3
5: ireturn
LineNumberTable:
line 6: 0
line 7: 4
}
SourceFile: "MethodAnalyzeDemo.java"
其中,关于 doSomething() 方法的字节码信息,如下图所示:
![图1](https://img-blog.csdnimg.cn/img_convert/d9fcf533dac99fcb23edacc5b910cd0f.png)
在 JVM 中,一个线程为一个栈,一个栈由多个栈桢组成,一个栈桢对应一个方法。
因此,doSomething() 方法在栈帧中的结构,如下图所示:
![图2](https://img-blog.csdnimg.cn/img_convert/106cffd1c257a2a256151eb7779a4682.png)
接下来,对 doSomething() 方法的每个部分进行讲述。
descriptor: (II)I
descriptor: (II)I
descriptor 表示方法的描述,(II) 表示 doSomething() 方法有两个 int 类型的形参,I 表示方法的返回值是 int 类型。
flags: ACC_PUBLIC
flags: ACC_PUBLIC
flags 表示方法的访问标志,ACC_PUBLIC 表示 doSomething() 方法的访问标志为 public。
Code
Code:
stack=2, locals=4, args_size=3
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: iload_3
5: ireturn
LineNumberTable:
line 6: 0
line 7: 4
Code 是方法表,表示 Java 方法经过编译后的字节码指令,就是以字节码的形式表达 Java 方法的执行过程。
stack=2, locals=4, args_size=3
表示方法在栈帧的基本信息,具体说明如下所示。
-
stack
操作数栈的深度
-
locals
局部变量表的大小
-
args_size
方法形参的数量,例如 doSomething() 方法的 args_size:
args_size = 3 = int i + int j + 对象实例的引用
对象实例的引用,可以理解为,通过 this 关键字访问此方法所属的对象。
举个例子,我们日常开发中经常使用到的 this.i = i;
public class Demo {
int i;
public setI(int i) {
this.i = i;
}
}
0: iload_1 …… 5: ireturn
表示方法执行的指令,就是 int result = i + j; 和 return result; 这两行代码对应的指令。
-
0: iload_1
将局部变量表中第二个变量(i)加载到操作数栈,这个变量是 int 类型。
-
1: iload_2
将局部变量表中第三个变量(j)加载到操作数栈,这个变量是 int 类型。
-
2: iadd
将操作数栈中栈顶的两个 int 类型变量(i,j)出栈,把它们相加的结果加载到栈顶(i + j)。
-
3: istore_3
将操作数栈中栈顶的 int 类型变量(就是 iadd 的结果)进行出栈,并且赋值局部变量表的第四个变量(result)。
-
4: iload_3
将局部变量表中第四个变量(result)加载到操作数栈,这个变量是 int 类型。
-
5: ireturn
将操作数栈中栈顶的 int 类型变量(result)返回。
0: iload_1 …… 5: ireturn 的执行过程,如下图所示:
![图3](https://img-blog.csdnimg.cn/img_convert/ab09b162f88d49d5ee3f12d90c4a9a54.png)
为什么没有 iload_0 ?
因为,构造方法或实例方法(非静态方法),对象实例的引用(this)会存放在索引为 0 的位置,其余参数按照参数表顺序进行排列。
而且,如果方法修改为如下所示,那么 0: iload_1 就会变成 0: iload_2,因为 String str 位于局部变量表索引为 1 的位置,int i 位于局部变量表索引为 2 的位置。
LineNumberTable
表示 java 源代码(.java文件)的行号和字节码(.class文件)的行号之间的对应关系,主要是方便在异常发生的时候,在堆栈中显示出源代码出错的行号;以及在调试过程中,按照源代码的行号设置断点。
LineNumberTable 不是运行时必需的属性,通过 -g:none 参数取消,-g:lines 参数生成(默认)。
line 6: 0、line 7: 4 的含义,如下图所示:
![图4](https://img-blog.csdnimg.cn/img_convert/7c13f8236c2e7ece83305ff9a70a2612.png)