文章目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4d81b4dc92086469423c2443c0eeef38.png)
类文件结构
一个简单的 HelloWorld.java
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
运行后使用WinHex将其字节码文件HelloWorld.class打开:
上面打开的就是我们编译后的HelloWorld.class二进制字节码文件,我么要做的就是分析并能够看懂上面的内容。
事实上,现在有很多反编译软件,比如在IDEA的Terminal终端就可以将HelloWorld.class进行反编译,将上面看不懂的二进制数字反编译为看得懂的程序代码。
下面就是将上面的二进制字节码反编译后的结果:
F:\JVM虚拟机\JVM_黑马\JVM_01\out\production\JVM_01\cn\itcast\jvm\t5>javap -v HelloWorld.class
Classfile /F:/JVM虚拟机/JVM_黑马/JVM_01/out/production/JVM_01/cn/itcast/jvm/t5/HelloWorld.class
Last modified 2020-3-25; size 567 bytes
MD5 checksum 8efebdac91aa496515fa1c161184e354
Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
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 // cn/itcast/jvm/t5/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 Lcn/itcast/jvm/t5/HelloWorld;
#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 cn/itcast/jvm/t5/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
{
public cn.itcast.jvm.t5.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 Lcn/itcast/jvm/t5/HelloWorld;
public static void main(java.lang.String[]);
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;
}
SourceFile: "HelloWorld.java"
我们要做的就是直接通过字节码文件解读成反编译后的结果(不通过反编译工具)。
在下面的所有分析过程,都可以通过借助反编译帮助我们理解。
根据JVM规范,类文件结构如下:
ClassFile{
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
1. 魔数
0~3 字节,表示它是否是【class】类型的文件
2. 版本
4~7 字节,表示类的版本 00 34(52) 表示是 Java 8
3. 常量池
常量结构表:
8~9 字节,表示常量池长度,00 22 (34) 表示常量池有 #1~#33
项,注意 #0
项不计入,也没有值
第#1
项 0A(tag=10)表示一个 Method 信息,00 06 和 00 14(20) 表示它引用了常量池中 #6 和 #20 项来获得这个方法的【所属类】和【方法名】
第#2
项 09(tag=9) 表示一个 Field 信息,00 15(21)和 00 16(22) 表示它引用了常量池中 #21和 # 22 项来获得这个成员变量的【所属类】和【成员变量名】
第#3
项 08(tag=8) 表示一个字符串常量名称,00 17(23)表示它引用了常量池中 #23项
第#4
项 0A(tag=10)表示一个 Method 信息,00 18(24) 和 00 19(25) 表示它引用了常量池中 #24 和 #25项来获得这个方法的【所属类】和【方法名】
第#5
项 07(tag=7) 表示一个 Class 信息,00 1A(26) 表示它引用了常量池中 #26 项
第#6
项 07 (tag=7)表示一个 Class 信息,00 1B(27) 表示它引用了常量池中 #27 项
第#7
项 01(tag=1) 表示一个 utf8 串,00 06 表示长度,3c 69 6e 69 74 3e 是【 <init> 】
第#8
项 01 表示一个 utf8 串,00 03 表示长度,28 29 56 是【()V】其实就是表示无参、无返回值
第#9
项 01 表示一个 utf8 串,00 04 表示长度,43 6f 64 65 是【Code】
第#10
项 01 表示一个 utf8 串,00 0f(15) 表示长度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65是【LineNumberTable】
第#11
项 01 表示一个 utf8 串,00 12(18) 表示长度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 6162 6c 65是【LocalVariableTable】
第#12
项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【this】
第#13
项 01 表示一个 utf8 串,00 1d(29) 表示长度,是【Lcn/itcast/jvm/t5/HelloWorld;】
第#14
项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【main】
第#15
项 01 表示一个 utf8 串,00 16(22) 表示长度,是【([Ljava/lang/String;)V】其实就是参数为字符串数组,无返回值
第#16
项 01 表示一个 utf8 串,00 04 表示长度,是【args】
第#17
项 01 表示一个 utf8 串,00 13(19) 表示长度,是【[Ljava/lang/String;】
第#18
项 01 表示一个 utf8 串,00 0a(10) 表示长度,是【SourceFile】
第#19
项 01 表示一个 utf8 串,00 0f(15) 表示长度,是【HelloWorld.java】
第#20
项 0C(tag=12) 表示一个 【名+类型】,00 07 00 08 引用了常量池中 #7 #8 两项
第#21
项 07(tag=12) 表示一个 Class 信息,00 1C(28) 引用了常量池中 #28 项
第#22
项 0C(tag=12)表示一个 【名+类型】,00 1D(29) 00 1E(30)引用了常量池中 #29 #30两项
第#23
项 01 表示一个 utf8 串,00 0B(11) 表示长度,是【hello world】
第#24
项 07 表示一个 Class 信息,00 1F(31) 引用了常量池中 #31 项
第#25
项 0c 表示一个 【名+类型】,00 20(32) 00 21(33)引用了常量池中 #32 #33 两项
第#26
项 01 表示一个 utf8 串,00 1b(27) 表示长度,是【cn/itcast/jvm/t5/HelloWorld】
第#27
项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/Object】
第#28
项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/System】
第#29
项 01 表示一个 utf8 串,00 03 表示长度,是【out】
第#30
项 01 表示一个 utf8 串,00 15(21) 表示长度,是【Ljava/io/PrintStream;】
第#31
项 01 表示一个 utf8 串,00 13(19) 表示长度,是【java/io/PrintStream】
第#32
项 01 表示一个 utf8 串,00 07 表示长度,是【println】
第#33
项 01 表示一个 utf8 串,00 15(21) 表示长度,是【(Ljava/lang/String;)V】
4. 类的访问标识与继承信息
根据JVM类文件规范可以得到下面要分析的:前面u2代表每个信息占用的字节数
类索引用于确定这个类的全限定类名
父类索引用于确定这个类的父类的全限定类名
接口索引集合用于描述这个类实现了哪些接口
因为java实现单继承,多实现,因此父类只有一个,接口可以有多个。
21 表示该 class 是一个类,公共的(0021=0001+0020=public+super)
05 表示根据常量池中 #5 找到本类全限定名
06 表示根据常量池中 #6 找到父类全限定名
表示接口的数量,本类为 0(HelloWorld.java没有实现接口)
5. Field 信息
下面哟啊分析的就是类的成员变量的信息:
表示成员变量数量,本类为 0
在描述这些成员变量时,可能会用到字节码里面的类型表示:字节码为了更加紧凑压缩了大小用了更简洁的字符来表示这些数据类型。
标志符 | 含义 |
---|---|
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 基本数据类型void |
L ClassName; | 对象类型,如Ljava/lang/Object |
[ | 一维数组 |
6. Method信息
表示方法数量,本类为 2(构造方法和main方法)
一个方法由访问修饰符,名称,参数描述,方法属性数量,方法属性组成:
MethodFile{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
6.1 method-init(构造方法)
方法的访问标识表:
00 01代表访问修饰符(本类中是方法为 public)
引用了常量池 #07 项作为方法名称
引用了常量池 #08 项作为方法参数描述
代表方法属性数量,本方法是 1
目前构造方法的访问修饰符,名称,参数描述,方法属性数量都已分析完:
对于属性信息(比如方法体对应的就是Code属性),开始一定通过一个u2字节的索引确定属性
u2 attribute_name_index
00 09 表示引用了常量池 #09 项,发现是【Code】
属性
下面进入【Code】
属性表分析属性:
CodeAttribute{
u2 attribute_name_index //这个已分析
u4 attribute_length
u2 max_stack
u2 max_locals
u4 code_length
code_length code
u2 exception_table_length
exception_info exception_count
u2 attributes_count
attribute_info attributes
}
00 00 00 2f 表示此属性的长度是 47
00 01 表示【操作数栈】最大深度(max_stack)
00 01 表示【局部变量表】最大槽(slot)数(max_locals)
00 00 00 05表示字节码长度
2a b7 00 01 b1 是字节码指令
00 代表方法抛出的异常为0
00 02 表示方法细节属性数量(即Code属性中的属性,同也是方法的属性),本例是 2
同样对于属性信息先通过一个u2字节的索引确定属性:
u2 attribute_name_index
00 0a 表示引用了常量池 #10 项,发现是【LineNumberTable】属性
因此下面就进入【LineNumberTable】属性
:
这个属性是为了价格字节码的行号和java源码的行号进行对应
LineNumberTableAttribute{
u2 attribute_name_index //这个已分析
u4 attribute_length
u2 line_number_table_length
line_numer_info line_number_table
}
00 00 00 06 表示此属性的总长度,本例是 6
00 01 表示【LineNumberTable】长度
00 00 表示【字节码】行号 00 04 表示【java 源码】行号
至此Code属性中的第一个属性信息已经分析完毕:
下面分析Code属性中的第二个属性:
00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性
进入【LocalVariableTable】属性表:
LocalVariableTableAttribute{
u2 attribute_name_index //这个已分析
u4 attribute_length
u2 local_variable_table_length
local_variable_info local_variable_table
}
00 00 00 0c 表示此属性的总长度,本例是 12
local_variable_info项目代表了一个栈帧与源码中的局部变量的关联
00 00 表示局部变量生命周期开始,相对于字节码的偏移量
00 05 表示局部变量覆盖的范围长度
00 0c 表示局部变量名称,本例引用了常量池 #12 项,是【this】
00 0d 表示局部变量的类型,本例引用了常量池 #13 项,是【Lcn/itcast/jvm/t5/HelloWorld;】
00 00 表示局部变量占有的槽位(slot)编号,本例是 0
至此整个构造方法分完毕。
6.2 method-main(主函数)
按照分析构造方法的思路去分析即可:
7. 附加属性
00 01 表示附加属性数量
00 12 表示引用了常量池 #18 项,即【SourceFile】
00 00 00 02 表示此属性的长度
00 13 表示引用了常量池 #19 项,即【HelloWorld.java】