解读HelloWorld字节码
上一篇文章我们拿到了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规范
推荐阅读
-
Java 将字符串动态生成字节码的实现方法
-
通过java字节码分析学习对象初始化顺序
-
Java 将字符串动态生成字节码的实现方法
-
Nutz:重新发明*:自己动手,用字节码工具做一个Aop拦截器 博客分类: Nutz AOPJavaF#SVNGoogle
-
快速修改字节码并重打jar包
-
试编制一个程序,把Ax中的16进制数转换为ASCII码,并将对应的ASCII码依次存放到MEM数组中的四个字节中
-
opcache PHP新的字节码缓存扩展详解
-
python字节码,java字节码,十六进制相互转换
-
java字节码理解--Java bytecode:翻译和解读
-
java中return与finally的执行顺序分析(根据字节码分析)