Hello World
一个最简单的Hello World程序源代码如下:
package top.zysite;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
通过javac -parameters -d . HelloWorld.java
命令编译为HelloWorld.class 文件后,将其拖入WinHex,内容如下:
类文件结构
根据 JVM 规范 ,一个类文件结构如下:
ClassFile{
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[count];
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];
}
注:上诉的 u4 u2 代表占用4个、2个字节。
魔数
字节码0~3个字节为魔数,也就是u4 magic
部分,用于标识文件类型为class
。
根据上述字节码内容,class文件魔数为CA FE BA BE
。
版本
字节码4~7个字节为版本,也就是u2 minor_version u2 major_version
部分,表示Java小版本号和主版本号。
根据上述字节码内容,版本为00 00 00 34
。小版本为00 00,主版本为00 34(十进制为52)代表Java 8。
常量池
字节码8~9个字节为常量池长度,也就是u2 constant_pool_count
部分。
根据上述字节码内容,常量池长度为00 22
,十进制为34,表示常量池有1-33项(不包括第0项)。
常量池含义表:
ConstantType | Value(十六进制) | Value(十进制) |
---|---|---|
CONSTANT_Class | 07 | 7 |
CONSTANT_Fieldref | 09 | 9 |
CONSTANT_Methodref | 0A | 10 |
CONSTANT_InterfaceMethodref | 0B | 11 |
CONSTANT_String | 08 | 8 |
CONSTANT_Integer | 03 | 3 |
CONSTANT_Float | 04 | 4 |
CONSTANT_Long | 05 | 5 |
CONSTANT_Double | 06 | 6 |
CONSTANT_NameAndType | 0C | 12 |
CONSTANT_Utf8 | 01 | 1 |
CONSTANT_MethodHandle | 0F | 15 |
CONSTANT_MethodType | 10 | 16 |
CONSTANT_InvokeDynamic | 12 | 18 |
紧接着常量池长度后的内容为字符串常量池具体内容,也就是cp_info constant_pool[count]
部分。
常量池的第1项,也就是00 22
之后的一个字节为0A
,根据上表,得知表示的是CONSTANT_Methodref
类型的信息,用于描述方法,紧接其后的四个字节00 06 00 14
,前两个字节00 06 (十进制为6)表示引用常量池中第6项来表示方法的所属类
。后两个字节00 14(十进制为20)表示引用常量池中第20项来表示方法的方法名
。
常量池的第2项,09
表示的是CONSTANT_Fieldref
类型的信息,用于描述成员变量,紧接其后的四个字节00 15 00 16
,前两个字节00 15(十进制为21)表示引用常量池中第21项来表示所属类
。后两个字节 00 16(十进制为22) 表示引用常量池中第22项来表示成员变量名
。
以此类推进行分析。根据前面的信息,得知共33项。
使用WinHex还有一个好处,它会同时将十六进制对照ANSCII码进行翻译:
分析到图中选中区域时,查表得知01
表示CONSTANT_Utf8
类型,即utf8串,其后一个字节00 06
表示串的长度(十进制为6),紧接着的6个字节即为串内容的ANSCII码值,可以看到内容为<init>
。
访问标识和继承信息
紧接常量池后的是访问标识,也就是u2 access_flags
部分。
访问标识含义表:
Flag Name | Value(十六进制) | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public |
ACC_FINAL | 0x0010 | 声明为final |
ACC_SUPER | 0x0020 | 当被特殊指令调用时,特别对待超类方法 |
ACC_INTERFACE | 0x0200 | 表示是一个接口而不是类 |
ACC_ABSTRACT | 0x0400 | 声明为abstract |
ACC_SYNTHETIC | 0x1000 | 表示人工合成,不是源代码 |
ACC_ANNOTATION | 0x2000 | 表示为注解类型 |
ACC_ENUM | 0x4000 | 表示为枚举类型 |
紧接常量池后的两个字节为00 21
,表示由ACC_PUBLIC
+ ACC_SUPER
相加得出,表示为一个public
类。
后两个字节00 05
表示以字符串常量池中第5项表示本类的全限定名
。即为u2 this_class
部分。
再之后两个字节00 06
表示以字符串常量池中第6项表示父类的全限定名
。即为u2 super_class
部分。
再之后两个字节00 00
表示接口的数量为0。即为u2 interfaces_count
部分,由于为0,所以其后没有具体的接口相关信息。
成员变量
紧接接口信息后的两个字节为00 00
表示成员变量的数量,为0。在class文件中,成员变量的类型由更简洁的字符表示,其与Java源代码中的类型对应表如下:
Field Type(.class) | Field Type(.java) | 说明 |
---|---|---|
B | byte | byte类型 |
C | char | char类型 |
D | double | double类型 |
F | float | float类型 |
I | int | int类型 |
J | long | long类型 |
LClassName | reference | 类ClassName的引用类型 |
S | short | short类型 |
Z | boolean | boolean类型 |
[ | reference | 一维数组类型 |
方法信息
紧接成员变量后的两个字节为00 02
表示方法的数量,为2。
一个方法由 访问修饰符
,名称
,参数描述
,方法属性数量
,方法属性
组成。可根据上述同样的方法进行分析,可以得出<init>
方法和main
方法的方法信息。
附加属性
紧接方法信息后的是附加属性。这里主要记录的是源文件信息,如源文件的名称。