- new一个对象
Java代码
public class Hello { public static void main(String[] args) { Hello h = new Hello(); } }
然后使用下面的命令进行编译获得class文件
javac -g Hello.java
再使用下面的命令进行反编译查看字节码
javap -v Hello.class
获得的字节码详情
Classfile java/Hello.class Last modified 2021-1-11; size 389 bytes MD5 checksum 390d0db07ea1ea82ee68a82cb82c41ad Compiled from "Hello.java" public class Hello minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#19 // java/lang/Object."<init>":()V #2 = Class #20 // Hello #3 = Methodref #2.#19 // Hello."<init>":()V #4 = Class #21 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 LHello; #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = Utf8 args #15 = Utf8 [Ljava/lang/String; #16 = Utf8 h #17 = Utf8 SourceFile #18 = Utf8 Hello.java #19 = NameAndType #5:#6 // "<init>":()V #20 = Utf8 Hello #21 = Utf8 java/lang/Object { public Hello(); 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 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LHello; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class Hello 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: return LineNumberTable: line 3: 0 line 4: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; 8 1 1 h LHello; } SourceFile: "Hello.java"
首先是前七行
Classfile java/Hello.class Last modified 2021-1-11; size 272 bytes MD5 checksum adc98c8855d7cc843b3556c4e922d2ac Compiled from "Hello.java" public class Hello minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER
这7行的内容分别是Class文件所在的位置,最后的修改时间,文件的大小,MD5值,编译自那个文件,类的全名,jdk的版本号,主版本号(52就是jdk8),接着是该类的访问标志,ACC_PUBLIC表示是不是Public类型,ACC_SUPER是是否允许使用invokespecial字节码指令
再就是常量池
Constant pool: #1 = Methodref #4.#13 // java/lang/Object."<init>":()V #2 = Class #14 // Hello #3 = Methodref #2.#13 // Hello."<init>":()V #4 = Class #15 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 main #10 = Utf8 ([Ljava/lang/String;)V #11 = Utf8 SourceFile #12 = Utf8 Hello.java #13 = NameAndType #5:#6 // "<init>":()V #14 = Utf8 Hello #15 = Utf8 java/lang/Object
常量池中主要存放文本字符串、final常量,和符号引用,符号引用包括有类和接口的全限定名、字段的名称和描述符号,方法的名称和描述符
具体分析:
#1 = Methodref #4.#13 // java/lang/Object."<init>":()V #4 = Class #15 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #13 = NameAndType #5:#6 // "<init>":()V #15 = Utf8 java/lang/Object
这里的#1 = #4.#13,而#4=#5,#13 = #5.#6,所以我们能知道#1=java/lang/Object."<init>":()V ,表示的是该类的实例构造器的声明,因为Hello没有重写构造器,所以调用的是父类的构造方法,也就是Object的构造器方法,方法的返回值是void,也就是V
第三行类似 #3=Hello."<init>":()V
#2 = Class #14 // Hello #3 = Methodref #2.#13 // Hello."<init>":()V #5 = Utf8 <init> #6 = Utf8 ()V #13 = NameAndType #5:#6 // "<init>":()V #14 = Utf8 Hello
然后是类内部的方法描述
public Hello(); 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 1: 0
这个是自动生成的构造方法,是一个无参数无返回值的方法(descriptor: ()V),公开方法(flags: ACC_PUBLIC),cpde之后就是stack最大操作数栈,jvm后面会根据这个来分配栈帧的操作深度,locals局部变量所需的存储空间,这里为1是因为有一个this,arg_size:方法参数的个数,这里是1还是因为this,再然后是方法体内容,0,1,4是字节码的行号,LineNumberTable,描述源码行号与字节码行号之间的对应关系,通常还有LocalVariableTable 该属性的作用是描述帧栈中局部变量与源码中定义的变量之间的关系。如果没有生成这项信息,那么当别人引用这个方法时,将无法获取到参数名称,取而代之的是arg0, arg1这样的占位符。 start 表示该局部变量在哪一行开始可见,length表示可见行数,Slot代表所在帧栈位置,Name是变量名称,然后是类型签名。
这里指令的含义是:
aload_0 从本地变量0中加载一个对象引用到堆内存(这里的对象引用就是java.lang.Object)
invokespecial #1 表示调用引用对象的一个实例方法(java.lang.Object.init())
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class Hello 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: return LineNumberTable: line 3: 0 line 4: 8
new:创建Hello对象
dup:复制栈顶的引用值
invokespecial :执行对象初始化
astore_1:将引用地址值存储但编号为1的局部变量中
- 四则运算
首先是java代码
public class Hello { public static void main(String[] args) { int a = 1,b=1,c; c= a+b; c= a-b; c= a*b; c= a/b; } }
反编译后的字节码:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: iconst_1 //int类型1入栈,栈顶=1 1: istore_1 //将栈顶的int数值出栈并存入第二个局部变量 2: iconst_1 //int类型1入栈,栈顶=1 3: istore_2 //将栈顶的int数值出栈并存入第三个局部变量 4: iload_1 //将第二个局部变量值压入栈顶 5: iload_2 //将第三个局部变量值压入栈顶 6: iadd //将栈顶两int类型数相加,结果入栈 7: istore_3 //将栈顶的int数值出栈并存入第四个局部变量 8: iload_1 //将第二个局部变量值压入栈顶 9: iload_2 //将第三个局部变量值压入栈顶 10: isub //将栈顶两int类型数相减,结果入栈 11: istore_3 //将栈顶的int数值出栈并存入第四个局部变量 12: iload_1 //将第二个局部变量值压入栈顶 13: iload_2 //将第三个局部变量值压入栈顶 14: imul //将栈顶两int类型数相乘,结果入栈 15: istore_3 //将栈顶的int数值出栈并存入第四个局部变量 16: iload_1 //将第二个局部变量值压入栈顶 17: iload_2 //将第三个局部变量值压入栈顶 18: idiv //将栈顶两int类型数相除,结果入栈 19: istore_3 //将栈顶的int数值出栈并存入第四个局部变量 20: return LineNumberTable: line 3: 0 line 4: 4 line 5: 8 line 6: 12 line 7: 16 line 8: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 args [Ljava/lang/String; 2 19 1 a I 4 17 2 b I 8 13 3 c I
这次的运算,基本逻辑就是int值入栈(iconst),int值出栈(istore),完成变量a、b的声明和初始化,然后将a,b的值压入栈中,使用iadd,isub,imul,idiv进行四则运算,然后istore_3将结果存入c
其中要注意的是,store会删除栈顶值,load将局部变量压入操作数栈,不会删除局部变量中的值
- if 和for
Java代码
import java.util.ArrayList; import java.util.List; public class Hello { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for(int i = 0;i<10;i++){ list.add(i); } for(Integer a:list){ if(a%2 == 0){ System.out.println(a); } } } }
反编译之后的字节码
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: iconst_0 9: istore_2 10: iload_2 11: bipush 10 13: if_icmpge 33 16: aload_1 17: iload_2 18: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 21: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 26: pop 27: iinc 2, 1 30: goto 10 33: aload_1 34: invokeinterface #6, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 39: astore_2 40: aload_2 41: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 46: ifeq 78 49: aload_2 50: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 55: checkcast #9 // class java/lang/Integer 58: astore_3 59: aload_3 60: invokevirtual #10 // Method java/lang/Integer.intValue:()I 63: iconst_2 64: irem 65: ifne 75 68: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 71: aload_3 72: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 75: goto 40 78: return LineNumberTable: line 6: 0 line 7: 8 line 8: 16 line 7: 27 line 10: 33 line 11: 59 line 12: 68 line 14: 75 line 15: 78 LocalVariableTable: Start Length Slot Name Signature 10 23 2 i I 59 16 3 a Ljava/lang/Integer; 0 79 0 args [Ljava/lang/String; 8 71 1 list Ljava/util/List;
这一段字节码实现了第一个循环
8: iconst_0 9: istore_2 10: iload_2 11: bipush 10 13: if_icmpge 33 16: aload_1 17: iload_2 18: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 21: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 26: pop 27: iinc 2, 1 30: goto 10
8-10生成i变量并赋值为0,在将改变量压入栈顶,然后11行将10压入栈顶,在13行比较栈顶两个int的值大于等于后则跳转到33行,如果是小于的话接着到16行-26行,进行list的app的操作,然后使用27行的iiinc实现i的自增,最后是30行的goto实现跳转到第10行完成一次循环。
33: aload_1 34: invokeinterface #6, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 39: astore_2 40: aload_2 41: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 46: ifeq 78 49: aload_2 50: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 55: checkcast #9 // class java/lang/Integer 58: astore_3 59: aload_3 60: invokevirtual #10 // Method java/lang/Integer.intValue:()I 63: iconst_2 64: irem 65: ifne 75 68: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 71: aload_3 72: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 75: goto 40 78: return
这段代码实现了第二个循环加判断,这个地方用的是迭代器实现的循环,33-46行主要是通过调用迭代器的hasNext判断循环是否结束,如果结束了,46行可直接跳到78行结束循环,这个循环在75行使用goto跳到40行执行下一次循环
判断的地方首先是,49-64行首先是49-58 Interger的强转并压入栈中,然后是63-64的与2取模运算,然后是65行的ifne对计算结果也就是栈顶的判断是否为0,不为0则跳转到75行也就是条件不成立,这样就完成了条件判断if
- 总结
经过上面几个实验可以总结出:
- JVM运行字节码是基于栈、局部变量数组、常量池的操作,栈中的变量主要用于运算、判断,局部变量数组、常量池就是存放数据,
- 基本每次都需要先将变量中的数据load到栈中再进行运算,
- 如果想要新生成基本类型就需要使用const操作在栈中获得数据,然后store到局部变量表中,使用的时候再load
- 如果想要新生成对象需要使用new-dup-invokespecial生成对象再store到局部变量表中,使用invokevirtual 调用方法,如果是接口使用invokeinterface
-
store会删除栈顶值,load将局部变量压入操作数栈,不会删除局部变量中的值