Java 类文件结构

309 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天

Java 类文件结构

本文主要介绍一下Java类的文件结构组成及字节码常用查看方式及工具。

1、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];
}

其中u1、u2、u4分别代表1、2、4个字节无符号数(无符号数是基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。)。

2、结构说明

2.1、magic:

  魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。

  minor_version、major_version:

  分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。

2.2、constant_pool_count:

  常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。

2.3、constant_pool[]:

  常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。

2.4、access_flags:

  访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表:

标志名称含义作用对象
ACC_PUBLIC0x0001public类型类和接口
ACC_FINAL0x0010类为final类型只有类
ACC_SUPER0x0020使用新型的invokespecial寓意,JDK1.2之后编译出来为真类和接口
ACC_INTERFACE0x0200接口类型,不是类类型所有的接口,没有类
ACC_ABSTRACT0x0400抽象类型所有的接口,部分类
ACC_SYNTHETIC0x1000标识这个类并非有用户代码产生的
ACC_ANNOTATION0x2000标识注解
ACC_ENUM0x4000标识枚举

2.5、this_class:

  类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。

2.6、super_class:

   父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那 constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当 然,如果某个类super_class的值是0,那么它必定是java.lang.Object类,因为只有它是没有父类的。

2.7、interfaces_count:

  接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。

2.8、interfaces[]:

  接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量。

2.9、fields_count:

  字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。

2.10、fields[]:

  字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。

2.11、methods_count:

  方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。

2.12、methods[]:

  方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。

2.13、attributes_count:

  属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。

2.14、attributes[]:

  属性表,attributes表的每个项的值必须是attribute_info结构。

3、示例

3.1、源码

public class AddDemo {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c= a+b;
        System.out.println("c=" + c);
    }
}

3.2、CLASS文件的十六进制文件

IDEA中需要安装BinED插件,左边是16进制码,右边是转成10进制后对应的ASCII码值。可参考ASCII,一般人看到就懵逼了,我也很懵逼,表示看不懂。

image-20221019135326094

3.3 、通过javap命令查看类字节码

如果想看懂可以通过jdk自带的javap命令进行查看,通过javap -v 类绝对路径/AddDemo.class即可输出。相关指令信息请参考JVM操作码指令

Classfile /Users/zhangjian/works/workspace_home/docker-demo/target/classes/com/jianjang/docker/study/base/AddDemo.class
  Last modified 2022-10-19; size 876 bytes
  MD5 checksum bad782a180920f88bf4a661d879fb8bf
  Compiled from "AddDemo.java"
public class com.jianjang.docker.study.base.AddDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#30        // java/lang/Object."<init>":()V
   #2 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #33            // java/lang/StringBuilder
   #4 = Methodref          #3.#30         // java/lang/StringBuilder."<init>":()V
   #5 = String             #34            // c=
   #6 = Methodref          #3.#35         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Methodref          #3.#36         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #8 = Methodref          #3.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #38.#39        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Class              #40            // com/jianjang/docker/study/base/AddDemo
  #11 = Class              #41            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/jianjang/docker/study/base/AddDemo;
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               args
  #22 = Utf8               [Ljava/lang/String;
  #23 = Utf8               a
  #24 = Utf8               I
  #25 = Utf8               b
  #26 = Utf8               c
  #27 = Utf8               MethodParameters
  #28 = Utf8               SourceFile
  #29 = Utf8               AddDemo.java
  #30 = NameAndType        #12:#13        // "<init>":()V
  #31 = Class              #42            // java/lang/System
  #32 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #33 = Utf8               java/lang/StringBuilder
  #34 = Utf8               c=
  #35 = NameAndType        #45:#46        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #36 = NameAndType        #45:#47        // append:(I)Ljava/lang/StringBuilder;
  #37 = NameAndType        #48:#49        // toString:()Ljava/lang/String;
  #38 = Class              #50            // java/io/PrintStream
  #39 = NameAndType        #51:#52        // println:(Ljava/lang/String;)V
  #40 = Utf8               com/jianjang/docker/study/base/AddDemo
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               append
  #46 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #47 = Utf8               (I)Ljava/lang/StringBuilder;
  #48 = Utf8               toString
  #49 = Utf8               ()Ljava/lang/String;
  #50 = Utf8               java/io/PrintStream
  #51 = Utf8               println
  #52 = Utf8               (Ljava/lang/String;)V
{
  public com.jianjang.docker.study.base.AddDemo();
    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 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/jianjang/docker/study/base/AddDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: new           #3                  // class java/lang/StringBuilder
        14: dup
        15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        18: ldc           #5                  // String c=
        20: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        23: iload_3
        24: invokevirtual #7                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        27: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        30: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        33: return
      LineNumberTable:
        line 13: 0
        line 14: 2
        line 15: 4
        line 16: 8
        line 17: 33
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      34     0  args   [Ljava/lang/String;
            2      32     1     a   I
            4      30     2     b   I
            8      26     3     c   I
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "AddDemo.java"

3.4、通过IDE插件jclasslib Bytecode Viewer

也可以通过安装IDE傻瓜式插件jclasslib Bytecode Viewer进行查看。使用方法

  • 选择要查看的class文件
  • 打开“view” 菜单,选择“Show Bytecode With jclasslib” 选项。
  • 选择上述菜单项后 IDEA 中会弹出 jclasslib 工具窗口

image-20221019162310654

image-20221019161938345