通过HelloWorld读懂Java字节码(.class)文件结构

通过HelloWorld读懂Java字节码(.class)文件结构

leo 969 2021-04-10

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项)。

常量池含义表:

ConstantTypeValue(十六进制)Value(十进制)
CONSTANT_Class077
CONSTANT_Fieldref099
CONSTANT_Methodref0A10
CONSTANT_InterfaceMethodref0B11
CONSTANT_String088
CONSTANT_Integer033
CONSTANT_Float044
CONSTANT_Long055
CONSTANT_Double066
CONSTANT_NameAndType0C12
CONSTANT_Utf8011
CONSTANT_MethodHandle0F15
CONSTANT_MethodType1016
CONSTANT_InvokeDynamic1218

紧接着常量池长度后的内容为字符串常量池具体内容,也就是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码进行翻译:

WinHex对照ANSCII翻译

分析到图中选中区域时,查表得知01表示CONSTANT_Utf8类型,即utf8串,其后一个字节00 06表示串的长度(十进制为6),紧接着的6个字节即为串内容的ANSCII码值,可以看到内容为<init>

访问标识和继承信息

紧接常量池后的是访问标识,也就是u2 access_flags部分。

访问标识含义表:

Flag NameValue(十六进制)含义
ACC_PUBLIC0x0001声明为public
ACC_FINAL0x0010声明为final
ACC_SUPER0x0020当被特殊指令调用时,特别对待超类方法
ACC_INTERFACE0x0200表示是一个接口而不是类
ACC_ABSTRACT0x0400声明为abstract
ACC_SYNTHETIC0x1000表示人工合成,不是源代码
ACC_ANNOTATION0x2000表示为注解类型
ACC_ENUM0x4000表示为枚举类型

紧接常量池后的两个字节为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)说明
Bbytebyte类型
Ccharchar类型
Ddoubledouble类型
Ffloatfloat类型
Iintint类型
Jlonglong类型
LClassNamereferenceClassName的引用类型
Sshortshort类型
Zbooleanboolean类型
[reference一维数组类型

方法信息

紧接成员变量后的两个字节为00 02表示方法的数量,为2。

一个方法由 访问修饰符名称参数描述方法属性数量方法属性组成。可根据上述同样的方法进行分析,可以得出<init>方法和main方法的方法信息。

附加属性

紧接方法信息后的是附加属性。这里主要记录的是源文件信息,如源文件的名称。

官方文档