2020年11月30日

深入理解类文件结构

点击数:8

写在前面

我们都知道 JVM 并不能直接运行 Java 源文件,而是开发者通过 JDK 自带的工具命令 javac将 Java 源文件编译成 class 字节码文件,也就是二进制文件,然后供JVM加载并使用。

为了深入学习这一块的内容,先创建类 User

  • User.java
package com.openmind;

/**
 * jishuzhan
 *
 * @author zhoujunwen
 * @date 2019-11-17
 * @time 20:28
 * @desc
 */
public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

类文件结构

Class类文件结构

编译User.java类,我们用 Sublime Text 打开 User.class 字节码文件,可以看到如下十六进制代码如下:

cafe babe 0000 0034 0021 0a00 0500 1c09
0004 001d 0900 0400 1e07 001f 0700 2001
0004 6e61 6d65 0100 124c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b01 0003 6167
6501 0001 4901 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 0013 4c63 6f6d
2f6f 7065 6e6d 696e 642f 5573 6572 3b01
0007 6765 744e 616d 6501 0014 2829 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 0773 6574 4e61 6d65 0100 1528 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
2956 0100 104d 6574 686f 6450 6172 616d
6574 6572 7301 0006 6765 7441 6765 0100
0328 2949 0100 0673 6574 4167 6501 0004
2849 2956 0100 0a53 6f75 7263 6546 696c
6501 0009 5573 6572 2e6a 6176 610c 000a
000b 0c00 0600 070c 0008 0009 0100 1163
6f6d 2f6f 7065 6e6d 696e 642f 5573 6572
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0400 0500 0000 0200 0200
0600 0700 0000 0200 0800 0900 0000 0500
0100 0a00 0b00 0100 0c00 0000 2f00 0100
0100 0000 052a b700 01b1 0000 0002 000d
0000 0006 0001 0000 000b 000e 0000 000c
0001 0000 0005 000f 0010 0000 0001 0011
0012 0001 000c 0000 002f 0001 0001 0000
0005 2ab4 0002 b000 0000 0200 0d00 0000
0600 0100 0000 1000 0e00 0000 0c00 0100
0000 0500 0f00 1000 0000 0100 1300 1400
0200 0c00 0000 3e00 0200 0200 0000 062a
2bb5 0002 b100 0000 0200 0d00 0000 0a00
0200 0000 1400 0500 1500 0e00 0000 1600
0200 0000 0600 0f00 1000 0000 0000 0600
0600 0700 0100 1500 0000 0501 0006 0000
0001 0016 0017 0001 000c 0000 002f 0001
0001 0000 0005 2ab4 0003 ac00 0000 0200
0d00 0000 0600 0100 0000 1800 0e00 0000
0c00 0100 0000 0500 0f00 1000 0000 0100
1800 1900 0200 0c00 0000 3e00 0200 0200
0000 062a 1bb5 0003 b100 0000 0200 0d00
0000 0a00 0200 0000 1c00 0500 1d00 0e00
0000 1600 0200 0000 0600 0f00 1000 0000
0000 0600 0800 0900 0100 1500 0000 0501
0008 0000 0001 001a 0000 0002 001b 
  • class文件是一组以 8 字节为基础的二进制流,用 u1,u2,u4,u8分别表示 1 个字节,2 个字节,4 个字节,8 个字节的无符号数,采用 Big-edian 形式,即高位字节在前
  • 各个数据项严格按照顺序紧凑排列在class文件中
  • class文件中没有任何分隔符,这使得class文件存储的几乎都是可执行代码(在class文件中注释信息已经不复存在)

一个class文件完整地描述了Java源文件的各种信息,Oracle JVM规范中的4.1 The ClassFile Structure 详细定义了一个标准class文件的结构,如下:

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 个字节是2个16进制位,也就是说上面 cafe babe 是 4 个字节,这 4 个字节称之为“魔数”。

1 个字节是 8 位二进制位,表示的范围是 xxxxxxxx,也就是从 00000000-11111111,表示 0 到 255。1 位 16 进制数(用二进制表示为 xxxx)最多只能表示到 15 (即对应的十六进制 F),要表示到 255 就需要两个十六进制位。所以,1个字节=2个16进制字符,一个16进制位=0.5个字节

用图表表示 ClassFile 的数据结构:

class文件结构字节数

Optimizing Java 的作者编了一句顺口溜:My Very Cute Animal Turns Savage In Full Moon Areas(我可爱的动物在满月时变得野蛮)。

My Very Cute Animal Turns Savage In Full Moon Areas
M V C A T S I F M A
Magic Number Version Constant pool Access flags This class Super class Interface Feild Method Attribute

魔数

每个 class 文件的前四个字节称为魔数,它的唯一作用就是鉴定是否为一个合法的 class 文件。很多文件存储标准中都使用了魔数来进行身份识别的,譬如 WAV 语音文件的魔数也用 4 个字节表示为: 0x52494646,转为字符串为RIFF

User.class的 16 进制字节码可以看到,class 的魔数为:0xCAFEBABE,我们可以亲切的称呼为“咖啡宝贝”。

Class文件的版本

class 文件中第 5 到 第 8 个字节表示版本号,其中 5、6表示次版本,7、8表示主版本号。Java 版本号是从 45 开始的,JDK1.1 之后的每个JDK大版本发布时,主版本号都要加 1。我们看一看到主版本号为 0x0034,转为十进制表示为 52,由此可以计算出编译 class 文件的JDK版本为JDK8(52-45=7,7+1=8)。

JDK高本版能向下兼容以前的 class 版本,但不能运行以后的版本,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的 Class 文件,否则会抛出java.lang.UnsupportedClassVersionError

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据