以如下代码为例,对 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:UsersZeroDesktopdemo> 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."":()V #2 = Class #13 // com/zero/demo/MethodAnalyzeDemo #3 = Class #14 // java/lang/Object #4 = Utf8 #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 // " ":()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." ":()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() 方法的字节码信息,如下图所示:
在 JVM 中,一个线程为一个栈,一个栈由多个栈桢组成,一个栈桢对应一个方法。
因此,doSomething() 方法在栈帧中的结构,如下图所示:
接下来,对 doSomething() 方法的每个部分进行讲述。
descriptor: (II)Idescriptor: (II)I
descriptor 表示方法的描述,(II) 表示 doSomething() 方法有两个 int 类型的形参,I 表示方法的返回值是 int 类型。
flags: ACC_PUBLICflags: ACC_PUBLIC
flags 表示方法的访问标志,ACC_PUBLIC 表示 doSomething() 方法的访问标志为 public。
CodeCode: 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 的执行过程,如下图所示:
为什么没有 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 的含义,如下图所示:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)