美文网首页
Java class文件探索

Java class文件探索

作者: WqyJh | 来源:发表于2020-07-22 20:57 被阅读0次

    本文将通过对一个简单的Java源文件,编译成的class文件进行分析,以探索Java class文件的格式。

    package ex3;
    
    public class ConstantTest {
        int a;
        public void inc() {
            ++a;
        }
    }
    

    将这个Java代码保存为ConstantTest.java,使用javac ConstantTest.java,将其编译成ConstantTest.class字节码文件。使用十六进制查看器hexdump,查看其内容hexdump -C ConstantTest.class。现在看不懂没关系,下面我们逐字节分析。

    00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|
    00000010  00 03 00 0f 07 00 10 07  00 11 01 00 01 61 01 00  |.............a..|
    00000020  01 49 01 00 06 3c 69 6e  69 74 3e 01 00 03 28 29  |.I...<init>...()|
    00000030  56 01 00 04 43 6f 64 65  01 00 0f 4c 69 6e 65 4e  |V...Code...LineN|
    00000040  75 6d 62 65 72 54 61 62  6c 65 01 00 03 69 6e 63  |umberTable...inc|
    00000050  01 00 0a 53 6f 75 72 63  65 46 69 6c 65 01 00 11  |...SourceFile...|
    00000060  43 6f 6e 73 74 61 6e 74  54 65 73 74 2e 6a 61 76  |ConstantTest.jav|
    00000070  61 0c 00 07 00 08 0c 00  05 00 06 01 00 10 65 78  |a.............ex|
    00000080  33 2f 43 6f 6e 73 74 61  6e 74 54 65 73 74 01 00  |3/ConstantTest..|
    00000090  10 6a 61 76 61 2f 6c 61  6e 67 2f 4f 62 6a 65 63  |.java/lang/Objec|
    000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
    000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|
    000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|
    000000d0  00 00 00 01 00 0a 00 00  00 06 00 01 00 00 00 03  |................|
    000000e0  00 01 00 0b 00 08 00 01  00 09 00 00 00 27 00 03  |.............'..|
    000000f0  00 01 00 00 00 0b 2a 59  b4 00 02 04 60 b5 00 02  |......*Y....`...|
    00000100  b1 00 00 00 01 00 0a 00  00 00 0a 00 02 00 00 00  |................|
    00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
    0000011f
    

    先看看class文件的反汇编内容javap -v ContantTest.class

    public class ex3.ConstantTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#15         // ex3/ConstantTest.a:I
       #3 = Class              #16            // ex3/ConstantTest
       #4 = Class              #17            // java/lang/Object
       #5 = Utf8               a
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               inc
      #12 = Utf8               SourceFile
      #13 = Utf8               ConstantTest.java
      #14 = NameAndType        #7:#8          // "<init>":()V
      #15 = NameAndType        #5:#6          // a:I
      #16 = Utf8               ex3/ConstantTest
      #17 = Utf8               java/lang/Object
    {
      int a;
        descriptor: I
        flags:
    
      public ex3.ConstantTest();
        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 3: 0
    
      public void inc();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field a:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field a:I
            10: return
          LineNumberTable:
            line 6: 0
            line 7: 10
    }
    

    class文件结构

    Class文件是一组以8bit字节为基础单位的二进制流, 各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。 当遇到需要占用8bit以上空间的数据项时,则会按照高位在前(大端序)的方式分割成若干个字节进行存储。

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

    Class文件格式可以用上面这个结构体来表示,其中的u2/u4表示2字节和4字节的数据。我们将其分为以下几个部分来探讨。

    • magic
    • versioin
    • 常量池
    • access_flags
    • 类索引、父类索引与接口索引集合
    • 字段表
    • 方法表
    • 属性表

    magic

    magic是一个四字节的数据,它唯一的功能是确定这个文件是否为一个能被虚拟机接受的Class文件,它的值是固定的0xCAFEBABE,是Cafe Babe的意思,这是Java在设计之初就已经决定了的。

    使用hexdump -C XXX.class | head -n 2 可以查看class文件的十六进制表示,可以看到它的前4字节是ca fe ba be。

    00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|
    

    注意:大端序与我们的阅读顺序相同。例如0x12345678中12是高位字节,78是低位字节,而class文件中的第1个字节是低位地址,第4个字节是高位地址,大端序中高位字节放在低位地址,即12放在第1个字节,78放在第4个字节。如果是小端序,就要反过来,在文件中看到的就是78 56 45 12。

    Linux中的file命令可以用来判断文件类型,它可以利用这个magic来判断一个文件是不是class文件。

    version

    紧接着magic的4个字节存储的是Class文件的版本号: 第5和第6个字节是minor_version , 第7和第8个字节是major_version

    Java的版本号是从45开始的, JDK 1.1之后的每个JDK大版本发布主版本号向上加1( JDK 1.0~1.1使用了45.0~45.3的版本号),高版本的JDK能向下兼容以前版本的Class文件, 但不能运行以后版本的Class文件, 因为《 Java虚拟机规范》 在Class文件校验部分明确要求了即使文件格式并未发生任何变化, 虚拟机也必须拒绝执行超过其版本号的Class文件。

    在上面的例子中,minor_version为00 00,而major_version为00 34即52,因此该class文件版本号为52.0,是由JDK 8编译而来。

    关于次版本号, 曾经在现代Java( 即Java 2)出现前被短暂使用过, JDK 1.0.2支持的版本45.0~45.3( 包括45.0~45.3),JDK 1.1支持版本45.0~45.65535, 从JDK 1.2以后, 直到JDK 12之前次版本号均未使用, 全部固定为零。 而到了JDK 12时期, 由于JDK提供的功能集已经非常庞大, 有一些复杂的新特性需要以“公测”的形式放出, 所以设计者重新启用了副版本号, 将它用于标识“技术预览版”功能特性的支持。 如果Class文件中使用了该版本JDK尚未列入正式特性清单中的预览功能, 则必须把次版本号标识为65535, 以便Java虚拟机在加载类文件时能够区分出来。

    常量池

    constant_pool_count是一个u2类型的数据,代表了常量池容量计数器, 这个容量计数是从1而不是0开始的。如上面例子中的00 12即十进制18,那么常量池的索引范围为1~17。在Class文件格式规范制定之时, 设计者将第0项常量空出来是有特殊考虑的, 这样做的目的在于, 如果后面某些指向常量池的索引值的数据在特定情况下
    需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。

    常量池中主要存放两大类常量: 字面量( Literal) 和符号引用( Symbolic References) 。

    • 字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。
    • 而符号引用则属于编译原理方面的概念, 主要包括下面几类常量:
      • 被模块导出或者开放的包( Package)
      • 类和接口的全限定名( Fully Qualified Name)
      • 字段的名称和描述符( Descriptor)
      • 方法的名称和描述符
      • 方法句柄和方法类型( Method Handle、 Method Type、 Invoke Dynamic)
      • 动态调用点和动态常量( Dynamically-Computed Call Site、 Dynamically-Computed Constant)

    以下是JDK 8支持的常量类型。

    常量类型 标志 描述
    CONSTANT_Class 7 类或接口的符号引用
    CONSTANT_Fieldref 9 字段的符号引用
    CONSTANT_Methodref 10 类中方法的符号引用
    CONSTANT_InterfaceMethodref 11 接口中方法的符号引用
    CONSTANT_String 8 字符串类型字面量
    CONSTANT_Integer 3 整型字面量
    CONSTANT_Float 4 浮点型字面量
    CONSTANT_Long 5 长整形字面量
    CONSTANT_Double 6 双精度浮点型字面量
    CONSTANT_NameAndType 12 字段或方法的部分符号引用
    CONSTANT_Utf8 1 UTF8编码的字符串
    CONSTANT_MethodHandle 15 方法句柄
    CONSTANT_MethodType 16 方法类型
    CONSTANT_InvokeDynamic 18 动态计算常量

    这些常量类型彼此都没有什么关系,它们是不同的结构体,对应着字节码中一段字节流,这段字节流的长度因常量类型而异。它们的共性是都由1字节的tag开头。

    00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|
    00000010  00 03 00 0f 07 00 10 07  00 11 01 00 01 61 01 00  |.............a..|
    00000020  01 49 01 00 06 3c 69 6e  69 74 3e 01 00 03 28 29  |.I...<init>...()|
    

    上面例子中常量池的第1项的tag为0a,是一个Methodref,它的结构如下,一共占5字节0a 00 04 00 0e。

    • class_index表示常量池中一个Class类型的下标,其值为00 04即第4项。
    • name_and_type_index表示常量池中一个NameAndType类型的下标,其值为00 0e即第14项。
    CONSTANT_Methodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    

    紧接着第2项的tag为09,是一个Fieldref,它的结构与Methodref一样,占用5字节09 00 03 00 0f。

    • class_index值为00 03即第3项。
    • name_and_type_index值为00 0f即第15项。
    CONSTANT_Fieldref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    

    第3项的tag为07,是一个Class,它的结构如下,一共占用3字节07 00 10。

    • name_index表示常量池中一个Utf8类型的下标,其值为00 10即第16项。
    CONSTANT_Class_info {
        u1 tag;
        u2 name_index;
    }
    

    第4项的tag为07,同上,指向第17项。

    第5项的tag为01,是一个Utf8,它的结构如下,长度是变化的。

    • length是字符串的字节数,其值为00 01即1。
    • bytes是一个字节数组,其值为0x61即97,是UTF-8编码表示的字符串a(英文字符的UTF-8编码与ASCII码相同)。看到这里,应该可以猜到,这个是代码中字段a的名称了。
    CONSTANT_Utf8_info {
        u1 tag;
        u2 length;
        u1 bytes[length];
    }
    

    第6项的tag01,同上,是一个字符串I

    第7项的tag01,同上,是一个字符串<init>

    第8项的tag01,同上,是一个字符串()V

    第9项的tag01,同上,是一个字符串Code

    第10项也是字符串LineNumberTable

    第11项也是字符串inc

    第12项也是字符串SourceFile

    第13项也是字符串ConstantTest.java

    00000070  61 0c 00 07 00 08 0c 00  05 00 06 01 00 10 65 78  |a.............ex|
    00000080  33 2f 43 6f 6e 73 74 61  6e 74 54 65 73 74 01 00  |3/ConstantTest..|
    00000090  10 6a 61 76 61 2f 6c 61  6e 67 2f 4f 62 6a 65 63  |.java/lang/Objec|
    000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
    

    第14项tag0c,是一个NameAndType,其结构如下,一共占用5字节0c 00 07 00 08。

    • name_index是常量池中一个Utf8_info的下标,表示方法名称,其值为00 07即7,即<init>
    • descriptor_index也是一个Utf8_info的下标,表示一个字段描述符(变量类型)或方法描述符(方法参数和返回值类型),其值为00 08即8,即()V表示方法没有参数,返回void类型。

    这一项合起来就是<init>:()V,描述的是该类中的一个方法,方法名称为<init>,没有参数,没有返回值。

    CONSTANT_NameAndType_info {
        u1 tag;
        u2 name_index;
        u2 descriptor_index;
    }
    

    第15项tag0c,同上,合起来是a:I,描述的是一个字段,字段名称为a,类型为int

    第16项tag01,是一个Utf8,长度为16,内容为65 78 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74,即ex3/ConstantTest

    第17项tag01,是一个Utf8,长度为16,内容为6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74,即java/lang/Object

    access_flags

    访问标志 描述
    ACC_PUBLIC 0x0001 public
    ACC_FINAL 0x0010 final
    ACC_SUPER 0x0020 JDK 1.0.2之后所有类都必须带上这个标志
    ACC_INTERFACE 0x0200 interface
    ACC_ABSTRACT 0x0400 abstract
    ACC_SYNTHETIC 0x1000 标识这个类不是由用户代码产生的
    ACC_ANNOTATION 0x2000 annotation
    ACC_ENUM 0x4000 enum

    在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或 者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract 类型;如果是类的话,是否被声明为final;等等。

    000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
    

    上面例子中的access_flags值为00 21即,ACC_PUBLIC|ACC_SUPER,表示这是一个public类。

    类索引、父类索引与接口索引集合

    类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合 (interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。类索 引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。

    由于Java语言不允许多 重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了 java.lang.Object外,所有Java类的父类索引都不为0。

    接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口,则应当是extends关键字)后的接口顺序从左到右排列在接口索引集合中。

    他们都是u2类型的数据,是指向常量池中一个Class类型的索引,而Class类型的索引则包含指向一个Utf8的索引。

    this_class是u2类型的数据,是指向常量池中一个Class类型的索引。上面例子中的值为00 03,即常量池中第3项,指向第16项,内容为ex3/ConstantTest

    super_class同上,值为00 04,即第4项,指向第17项,内容为java/lang/Object

    接口索引集合首先包含一个u2类型的数据,表示接口数量,然后向后寻找接口数量个指向Class类型的索引。例子中值为00 00,表示没有实现接口。

    字段表

    000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
    000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|
    

    fields_count是u2数据,表示类拥有多少个字段。在上面例子中值为00 01,表明有1个字段。

    field_info结构用于描述接口或者类中声明的变量。Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段可以包括的修饰符有字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否 强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、 字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    access_flags是方法的访问修饰符,与类的access_flags一样,都是u2类型。在上面例子中值为00 00,表明没有加修饰符。

    标志 描述
    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

    name_index是名称索引,指向常量池中一个Utf8,例子中值为00 05即第5项,是字符串a,表明这个字段名称为a

    descriptor_index是描述符索引,指向常量池中一个Utf8,例子中值为00 06,是字符串I,表明这个字段类型为int

    attributes_count是属性数量,值为00 00,表示没有属性。

    方法表

    000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|
    000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|
    

    方法表结构与字段表几乎一样。

    methods_count表示方法数量,在上面例子中值为00 02,即有两个方法,后面有两个method_info项。

    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_STRICT 0x0800 strictfp
    ACC_SYNTHETIC 0x1000 方法由编译器生成

    方法1

    access_flags值为00 01,表示是public方法。

    name_index值为00 07,指向字符串<init>,这是方法名称。

    descriptor_index值为00 08,指向字符串()V,表示方法没有参数,返回类型为void。

    attributes_count值为00 01,表示有1个attribute_info

    attribute_info结构如下,长度是变化的。

    attribute_info {
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }
    

    attribute_name_index值为00 09,指向字符串Code

    attribute_length值为00 00 00 1d即29,表明后面的info字段长度为29字节。

    000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|
    000000d0  00 00 00 01 00 0a 00 00  00 06 00 01 00 00 00 03  |................|
    

    info是Code属性,其结构定义如下。

    Code_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 max_stack;
        u2 max_locals;
        u4 code_length;
        u1 code[code_length];
        u2 exception_table_length;
        {   u2 start_pc;
            u2 end_pc;
            u2 handler_pc;
            u2 catch_type;
        } exception_table[exception_table_length];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    max_stack值为00 01,表明操作数栈的深度为1。

    max_locals值为00 01,表明局部变量数量为1。

    code_length值为00 00 00 05,表明代码长度为5。

    code值为2a b7 00 01 b1,代表的指令分别为

    • 2a aload_0
    • b7 00 01``invokespecial #00.#01`
    • b1 return

    exception_table_length值为00 00,代表异常表为空,即不抛出异常。

    attrbutes_count值为00 01,表明属性表中有一个属性。

    attribute_name_index值为00 0a,指向字符串LineNumberTable。LineNumberTable属性描述java源码和字节码的对应关系,它并不是运行时必备的属性,但默认会生成到class文件中。可以用javac -g:none来禁用LineNumberTable,这样程序在抛出异常的时候将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。

    LineNumberTable_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 line_number_table_length;
        {   u2 start_pc;
            u2 line_number; 
        } line_number_table[line_number_table_length];
    }
    

    attribute_length值为00 00 00 06,表明后面还有6字节。

    line_number_table_length值为00 01,表明line_number_table中有一项。

    这一项的start_pc值为00 00,line_number值为00 03,表明java源代码中行号为3的位置对应着class中该方法的起始位置。

    方法2

    000000e0  00 01 00 0b 00 08 00 01  00 09 00 00 00 27 00 03  |.............'..|
    000000f0  00 01 00 00 00 0b 2a 59  b4 00 02 04 60 b5 00 02  |......*Y....`...|
    00000100  b1 00 00 00 01 00 0a 00  00 00 0a 00 02 00 00 00  |................|
    00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
    0000011f
    

    access_flags为00 01,表明是public方法。

    name_index值为00 0b,指向字符串inc,这是方法名称。

    descriptor_index值为00 08,指向字符串()V,表示方法没有参数,返回类型为void。

    attributes_count值为00 01,表示有1个attribute_info

    attribute_name_index值为00 09,指向字符串Code,因此这个属性也是Code属性。

    attribute_length值为00 00 00 27即39,表明后面的info字段长度为39字节。

    max_stack值为00 03,表明操作数栈的深度为3。

    max_locals值为00 01,表明局部变量数量为1。

    code_length值为00 00 00 0b,表明代码长度为11。

    code值为2a 59 b4 00 02 04 60 b5 00 02 b1,代表的指令分别为,显然这与inc方法相符。

    • 2a aload_0
    • 59 dup
    • b4 00 02 getfield #00.#02
    • 02 iconst_1
    • 60 iadd
    • b5 00 02 putfield #00.#02
    • b1 return

    exception_table_length值为00 00,代表异常表为空,即不抛出异常。

    attrbutes_count值为00 01,表明属性表中有一个属性。

    attribute_name_index值为00 0a,指向字符串LineNumberTable,表明是一个LineNumberTable属性。

    attribute_length值为00 00 00 0a,表明后面还有10字节。

    line_number_table_length值为00 02,表明line_number_table中有2项。

    第1项的start_pc值为00 00,line_number值为00 06,表明java源代码中行号为6的位置对应着code的起始位置。

    第2项的start_pc值为00 0a,line_number值为00 07,表明java源代码中行号为7的位置对应着code第10字节的位置即return

    属性表

    00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
    0000011f
    

    attributes_count值为00 01,表明有一个属性。

    attribute_name_index值为00 0c,指向字符串SourceFile,表明是一个SourceFile属性。其结构如下,占用8字节。

    SourceFile_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 sourcefile_index;
    }
    

    attribute_length值为00 00 00 02,表明attribute_length之后的属性内容一共占2字节。

    sourcefile_index值为00 0d,指向常量池中第13向,即字符串ConstantTest.java

    至此,字节码文件中的每一个字节都解析完了!

    总结

    • 本文对class文件进行了逐字节分析,做了javap类似的工作,也得到了相符的结果。
    • Cafe babe很有趣,Java的设计者们太会了!
    • 各种定长结构体、变长结构体设计得非常巧妙,空间很紧凑,一点都没浪费。
    • 常量池非常重要,字节码指令中包含对常量池的引用,常量池中的数据也有递归引用。
    • 方法中的代码由一段Code属性描述,它就是一个字节数组。
    • 构造方法的名称为<init>

    了解了class文件结构之后,就要开始探索类加载机制了。

    参考

    相关文章

      网友评论

          本文标题:Java class文件探索

          本文链接:https://www.haomeiwen.com/subject/rmtxlktx.html