文章目录
首先在这里贴上源代码
简单的一个第一个程序,但是不影响学习class的内容(当然是简单的好分析 ^_^)
package ttst2;
public class Tests {
public static void main(String[] args) {
System.out.println("helloworld");
}
}
先把class文件也贴上
这里使用的sublime的hexviwer工具
之后就开始分析
class类结构
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count-1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods methods_count | |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
magic
每个class文件的开头.它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的Class文件。
caff babe
minor_version major_version
class文件的版本
0000 0034
minor_version:占2字节,次版本号,0x0000
majro_version:占2字节,主版本号,0x0034,转化为十进制为52,是使用JDK1.8编译的
constain_pool_count
常量池中常量数
好像是因为设计的时候将0保留,可能是为以后做保留
所以常量数有 constain_pool_count-1
0022
转为10进制 33个
constant_pool
根据常量池数据结构中的数值一个个对照
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 | 值为1 |
. | length | u2 | utf-8编码的字符串占用的字节数 |
. | bytes | u2 | 长度为length的utf-8编码的字符串 |
CONSTANT_Interger_info | tag | u1 | 值为3 |
. | bytes | u4 | 按照高位在前存储的int值 |
CONSTANT_Float_info | tag | u1 | 值为4 |
. | bytes | u4 | 按照高位在前存储的float值 |
CONSTANT_Long_info | tag | u1 | 值为5 |
. | bytes | u8 | 按照高位在前存储的long值 |
CONSTANT_Double_info | tag | u1 | 值为6 |
. | bytes | u8 | 按照高位在前存储的double值 |
CONSTANT_Class_info | tag | u1 | 值为7 |
. | index | u2 | 指向全限定名常量项的索引 |
CONSTANT_String_info | tag | u1 | 值为8 |
. | index | u2 | 指向字符串字面量的索引 |
CONSTANT_Fieldref_info | tag | u1 | 值为9 |
. | index | u2 | 指向声明字段的类或接口描述符 constant_class_info的索引项 |
. | index | u2 | 指向子段描述符constant_nameAndType的索引项 |
CONSTANT_Meghodref_info | tag | u1 | 值为10 |
. | index | u2 | 指向声明方法的类描述符 classinfo的索引 |
. | index | u2 | 指向子段描述符constant_nameAndType的索引项 |
CONSTANT_InterfaceMethodref_info | tag | u1 | 值为11 |
. | index | u2 | 指向声明方法的类描述符 classinfo的索引 |
. | index | u2 | 指向子段描述符constant_nameAndType的索引项 |
CONSTANT_NameAndType_info | tag | u1 | 值为12 |
. | index | u2 | 指向该子段或方法名常量的索引 |
. | index | u2 | 指向该子段或方法描述符常量项索引 |
CONSTANT_MethodHandle_info | tag | u1 | 值为15 |
. | reference_kind | u1 | 值必须在1-9之内,决定了方法句柄的类型,方法句柄类型值表示方法句柄字节码行为 |
. | regerence_index | u2 | 值必须是对常量池的有效索引 |
CONSTANT_MethodType_info | tag | u1 | 值为16 |
. | descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引出必须是CONSTANT_Utf8_info结构,表示方法的描述符 |
CONSTANT_InvokeDynameic_info | tag | u1 | 值为18 |
. | bootstrap_method_attr_index | u2 | 值必须是对当前class文件中引导方法表的bootstrap_methods[]数组的有效索引 |
. | name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引出的项必须是CONSTANT_NameAndType_info,表示方法名和方法描述符 |
class中常量池对应的所有16进制
这里贴上笔者自己分析
比如: 07 代表的是CONSTANT_Class_info,在对照表中找到CONSTANT_Class_info,根据它的长度,进行分析,如果表示标识是index,就是这个常量池对应的第几个
常量池
#1 07 class_info
0002 index #2
#2 01 utf8_info
000b 11
7474 7374 322f 5465 7374 73
ttst2/Tests
#3 07 class_info
0004 index #4
#4 01 utf8-info
0010 16
6a 6176 612f 6c61 6e67 2f4f 626a 6563 74
java/lang/Object
#5 01 utf8_info
0006 6
3c69 6e69 743e <init>
#6 01
0003 3
28 2956 ()V
#7 01
0004 4
43 6f64 65 Code
#8 0A Meghodref_info
0003 #3
0009 #9
#9 0c nameandtype_info
0005 #5
0006 #6
#10 01
000f 15
4c69 6e65 4e75 6d62 572 5461 626c 65
LineNumberTable
#11 01
0012 18
4C6F 6361 6C56 6172 6961 626C 6554 6162 6C65
LocalVariableTable
#12 01
0004 4
74 6869 73
this
#13 01
000d 13
4C74 7473 7432 2F54 6573 7473 3B
Lttst2/Tests;
#14 01
0004 4
6d61 696e
main
#15 01
0016 22
28 5B4C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B29 56
([Ljava/lang/String;)V
#16 09 Fieldref
0011 constant_class_info #17
0013 constant_nameAndType #19
#17 07 Class_info
0012 #18 全限定名常量项的索引
#18 01
0010 16
6A61 7661 2F6C 616E 672F 5379 7374 656D
java/lang/System
#19 0c NameAndType_info
0014 #20
0015 #21
#20 01
0003 3
6F75 74 out
#21 01
0015 21
4C6A 6176 612F 696F 2F50 7269 6E74 5374 7265 616D 3B
Ljava/io/PrintStream;
#22 08 String_info
0017 #23
#23 01
000a 10
68 656C 6C6F 776F 726C 64
helloworld
#24 0a Meghodref_info
0019 classinfo #25
001b constant_nameAndType #27
#25 07 Class_info
001a 指向全限定名常量项的索引 #26
#26 01
0013 19
6A61 7661 2F69 6F2F 5072 696E 7453 7472 6561 6D
java/io/PrintStream
#27 0c NameAndType_info
001c #28
001d #29
#28 01
0007 7
70 7269 6E74 6C6E
println
#29 01
0015 21
28 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 2956
(Ljava/lang/String;)V
#30 01
0004 4
61 7267 73
args
#31 01
0013 19
5B4C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B
[Ljava/lang/String;
#32 01
000a 10
536F 7572 6365 4669 6C65
SourceFile
#33 01
000a 10
54 6573 7473 2E6A 6176 61
Tests.java
访问标志
分析完常量池后就是访问标志
访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明final,只有类可以设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK 1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK 1.0.2之后编译出来的类的这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
但是我们发现下一个u2字节是 0x0021
我们发现表中好像没有,我们只需要计算的时候加上0x0020即可(这里不是单纯的相加),就是表中的ACC_SUPER
ACC_SUPER |ACC_PUBLIC = 0x0001 | 0x0020 = 0x0021
0x0021 //public
类索引、父类索引与接口索引集合,接口
this_class : 类索引,用于确定这个类的全限定名
super_class:父类索引,用于确定这个类父类的全限定名.Java语言不允许多重继承,故父类索引只有一个。
interfaces_count: 接口索引计数器(这里好像忘记写了继承接口了,反正是没有 *_*)
interfaces:这里没有接口,算了不管了(-_-),读者自己写一个实现接口分析一下
0021 public
0001 this_class ttst2/Tests
0003 super_class java/lang/Object
0000 interfaces_count
字段表集合
描述接口或者类中声明的变量,注意这里的变量只包括类变量和实例变量,而不包括方法中的局部变量.
哇,字段数为0。又不用一个一个对照了 #_#。
0000 fields_count 字段数量
这里贴上字段数据结构
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | access_flags | 1 | 字段访问标志 |
u2 | name_index | 1 | 字段简单名称 |
u2 | descriptor_index | 1 | 描述符 |
u2 | attributes_count | 1 | 属性数量 |
u2 | attributes | attributes_count | 属性值 |
字段中 access_flags和上方的访问标志相似
access_flags(fields)
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否enum |
其中的attributes和方法中的基本相似。
方法集合
0x0002 methods_count
methods_count: 方法数量
methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样:
methods数据结构
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法access_flags
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否为public |
ACC_PRIVATE | 0x0002 | 字段是否为private |
ACC_PROTECTED | 0x0004 | 字段是否为protected |
ACC_STATIC | 0x0008 | 字段是否为static |
ACC_FINAL | 0x0010 | 字段是否为final |
ACC_SYNCHRONIZED | 0x0020 | 字段是否为synchronized |
ACC_BRIDGE | 0x0040 | 方法是否是由编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定参数 |
ACC_NATIVE | 0x0100 | 字段是否为native |
ACC_ABSTRACT | 0x0400 | 字段是否为abstract |
ACC_STRICTFP | 0x0800 | 字段是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 字段是否为编译器自动产生 |
0001 access_flags; public
0005 name_index; #5 <init>
0006 descriptor_index; ()V (void)
0001 attributes_count; 属性表计数 init方法的属性
0007 attributes_name_index; Code //这个代表下方的code区
其中的index从上发的常量表中查找对应的字符串
attribute_info (code)
这里就是code,不太懂&_&
attribute_info{
u2 attributes_name_index
u4 attribute_length
u1 max_stack
u1 max_locals
u4 code_length
u1 code[code_length]
u2 exception_table_length
exception_info exception_table[exception_table_length]
u2 attributes_count
attribute_info attributes[attribute_count]
}
code区
0007 attributes_name_index
0000 002f attribute_length
0001 max_stack
0001 max_locals
0000 0005 code_length 字节码指令长度
2a code 字节码指令5个
b7
00
08
b1
0000 exception_table_length 异常数量
exception_table 异常表
code剩余的属性
0002 attribute_count 2个属性
其中code字节码指令是jvm指令,还有没学到,读者可以查看相关资料
下方的attribute_info代表code剩下的属性 0x0002代表下方还有2个属性
剩下的2个属性我们可以根据接下来的
attribute_name_index 找到常量池中对应的名称
属性表集合
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类文件、字段表、方法表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTale | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类文件、方法表、字段表 | 标识方法或字段是由编译器自动生成的 |
根据上表的可以找到对应的属性名称,之后根据对应的结构去分析
发现是LineNumberTable
000a attribute_name_index 000a #10
LineNumberTable
LineNumberTable属性表结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | line_number_table_length | 1 |
line_number_info | line_number_table | line_number_table_length |
lineNumberTable中的line_number_info结构
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | start_pc | 1 | 字节码行号 |
u2 | line_number | 1 | java源码行号 |
000a attribute_name_index 000a #10
LineNumberTable
0000 0006 attribute_length 属性长度
0001 line_number_table_length
line-number_table:
0000 start_pc 字节码行号
0004 line_number java源码行号
之后根据这个方法查找下一个属性
000b attribute_name_index
LocalVariableTable
根据上方属性表集合找到此属性
LocalVariableTable属性表结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | local_variable_table_length | 1 |
local_variable_info | local_variable_table | local_variable_table_length |
local_variable_info
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | start_pc | 1 | 局部变量的生命周期开始的字节码偏移量 |
u2 | length | 1 | 局部变量作用范围覆盖的长度 |
u2 | name_index | 1 | 局部变量的名称 |
u2 | descriptor_index | 1 | 这个局部变量的描述符 |
u2 | index | 1 | 这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为index和index+1两个 |
000b attribute_name_index
LocalVariableTable
0000 000c attribute_length 属性长度
0001 local_variable_table_length
local_variable_table_length:
0000 start_pc
0005 length; 代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度
000c name_index;
this 分别代表了局部变量的名称
000d descriptor_index; Lttst2/Tests; 局部变量的描述符
0000 index 这个局部变量在栈帧局部变量表中Slot的位置
到这里第一个方法就分析完成了。。
第二个方法
这个方法就是main方法,开始的access_flags就是8+1,分别对应public和static
0009 access_flags 8+1 static public
000E name_index
main
000f descriptor_index
([Ljava/lang/String;)V
0001 attributes_count
其中0x0001代表method的方法有1个
继续分析发现接下来又是一个code区
0007 attributes_name_index Code
0000 0037 attributes_length
0002 max_stack
0001 max_locals
0000 0009 code_length
b2 code 字节码指令9个
00
10
12
16
b6
00
18
b1
0000 exception_table_length
exception_table
0002 attributes_count 2个属性
这里又是两个属性,我敢大胆猜测,又是上面的两个code属性.(|^_😃)
第一个属性
000a attribute_name_index #10
LineNumberTable
0000 000a attribute_length
0002 line_number_table_length
line_number_table0
0000 start_pc 字节码行号
0006 line_number java源码行号
line_number_table1
0008 start_pc 字节码行号
0007 line_number java源码行号
第二个属性
000B attribute_name_index #8
LocalVariableTable
0000 000c attribute_length
0001 local_variable_table_length
local_variable_table
0000 start_pc
0009 length
001e name_index
args
001f descriptor_index
[Ljava/lang/String;
0000 line_number java源码行号
方法都分析完了,发现还有最后的attributes_count,attributes_info(打起精神,马上就完了*—*)
我推测就是 属性表中的SourceFile(我现在都不知道是不是%_%)
属性表集合
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类文件、字段表、方法表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTale | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类文件、方法表、字段表 | 标识方法或字段是由编译器自动生成的 |
classFile最后一个属性
0001 attribute_count
0020 attribute_name_index
SourceFile
0000 0002 attribute_length 2
0021
Tests.java
最后根据我们的分析和javap生成的对照,发现没有错误(^_^)
ps
1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。例如:“com.test.Test"类的全限定名为"com/test/Test;”
2,简单名称:没有类型和参数修饰的方法或字段名称。例如:"public void add(int a,int b){…}“该方法的简单名称为"add”,"int a = 123;“该字段的简单名称为"a”
3,描述符:描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示
4,Slot,虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot
5. 无符号数:是基本数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。