上一篇文章我们拿到了HelloWorld的字节码,这回我们来搞他,我稍微整理一下,我们一起来研究。
字节码的组成
1.首先是类主体的定义部分,包括了我们的类版本号,访问修饰符
public class HelloWorld minor version: 0//次版本号 major version: 52//主版本号52对应jdk1.8 flags: ACC_PUBLIC, ACC_SUPER
2.就是我们的常量池部分,程序运行的时候会形成一个表格,#号代表是表的位置,通过访问位置便可以查到这个位置对应的引用,我们看到引用的右边还是会有#号,这种就是要不断查表获取最原始的地址。
Constant pool: //常量池部分 #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello World! #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // HelloWorld #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 LHelloWorld; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 HelloWorld.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello World! #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 HelloWorld #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V
3.部分便是我们的方法了,我们的默认的构造方法也是一个方法,会在我们的字节码中体现出来。
构造方法:
public HelloWorld();//构造方法 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 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LHelloWorld;
main函数:
public static void main(java.lang.String[]);//main函数 descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 6: 0 line 7: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;
我们方法也是保护了方法名,参数,访问修饰符,包括方法的具体内容,和我们的实际代码会对应得上,LineNumberTable保存了字节码和源码之间的对应关系,LocalVariableTable则是我们诚信运行时候的局部变量,我们看到我们的main函数上面的args参数保存在了本地变量的部分。
字节码的运行过程
我们看到实际的代码运行其实关键的几句:
0: getstatic #2 3: ldc #3 5: invokevirtual #4 8: return
我们把涉及到的几个关键常量拿过来,//后面的注释其实是方便我们阅读的内容,实际执行的时候不会有的。
位置 | 内容 |
#2 | Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream |
#3 | String #23 // Hello World! |
#4 | Methodref #24.#25 |
#21 | Class #28 // java/lang/System |
#22 | #29:#30 // out:Ljava/io/PrintStream |
#23 | Utf8 Hello World! |
#24 | Class #31 // java/io/PrintStream |
#25 | NameAndType #32:#33 // println: |
#28 | Utf8 java/lang/System |
#29 | Utf8 out |
#30 | Utf8 Ljava/io/PrintStream |
#31 | Utf8 java/io/PrintStream |
#32 | Utf8 println |
#33 | Utf8 (Ljava/lang/String;)V |
程序运行过程如下:
getstatic #2 表示从2中获取静态变量,#2又是来自#21.#22 ,#22又是来自#29和#30,#29和#30就是我们的out::printStream
第一步指令就是拿到我们的输出对象流。
ldc #3是把我们的常量压入栈中,#3对应的是我们字符串Hello World
invokevirtual #4 是我们的方法引用,查表过去就是我们的#24.#25,
#24则是#31 java/io/PrintStream,#25则是拿到我们的println((Ljava/lang/String;)V),这里其实是在执行我们的打印操作。
连起来就是如下结果:
getstatic out::printStream //加载静态变量
ldc “Hello World” //"hello world"放入操作数栈
invokevirtual println((Ljava/lang/String;)V) //执行方法print(string)
实际的操作过程会伴随堆栈中的的数据变化,后面我们详细分析,虚拟机的指令用法需要参考jvm规范JVM规范
jdk8的参考:jvm8规范