JVM(四)类文件结构解析

Java Class 文件结构

如下图所示
image.png
对于以下 java 源文件代码

public class Charles implements ICharles{
  private String name;
  public void say() {
    System.out.println("charles");
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

生成的类文件字节码为
image.png
根据 class 文件结构分析字节码

1、 魔数(magic)

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

2、 版本号(minor_version, major_version)

minor_version 和 major_version 的值分别表示 Class 文件的副、主版本号,它们共同构成了 Class 文件的格式版本号。
image.png
其中,00 00(次版本号),00 31(主版本号),即十进制版本号 49,使用 JDK1.6 编译输出

3、常量池计数器(constant_pool_count)

常量池是 class 文件中非常重要的结构,它描述着整个 class 文件的字面量信息。常量池是由一组 constant_pool 结构体数组组成的,而数组的大小则由常量池计数器指定。常量池计数器 constant_pool_count 的值 =constant_pool 表中的成员数 + 1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被认为是有效的。

4、常量池数据区(constant_pool[contstant_pool_count-1])

代表常量池个数以及常量池信息,constant_pool 是一种表结构,它包含 Class 文件结构及其子结构中引用的所有字符串常量、 类或接口名、字段名和其它常量。常量池中的每一项都具备相同的格式特征,第一个字节作为类型标记用于识别该项是哪种类型的常量,称为 “tag byte” 。常量池的索引范围是 1 至 constant_pool_count-1。
常量池,主要分两种字面常量(Literal)和符号引用(Symbolic reference) 。
其中符号引用属于编译原理方面概念,主要包含以下三类常量:

  1. 类和接口的权限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符
    所有的常量池项都具有如下通用格式:
    image.png
    常量池中,每个 cp_info 项的格式必须相同,它们都以一个表示 cp_info 类型的单字节“tag”项开头。后面 info[]项的内容 tag 由的类型所决定。tag 有效的类型和对应的取值在表 4.3 列出。每个 tag 项必须跟随 2 个或更多的字节,这些字节用于给定这个常量的信息,附加字节的信息格式由 tag 的值决定。
    主要有以下类型
    image.png
    本主要就 CONSTANT_Class_info 和 CONSTANT_Utf8_info 进行讨论。
    image.png
    本例中 class 的常量池为
    image.png
其中常量池的大小为0x28即为(2*16+8)-1 = 39,索引为0不会用到。
07 00 02 (CONSTANT_Class_info)
#1 = Class    =2 (第一个常量为类信息,name索引为2)
01 00 13 63 6f 6d 2f 63 68 61 72 6c 65 73 2f 43 68 61 72 6c 65 73
00 13 字符串长度为19个字节
63 6f 6d 2f 63 68 61 72 6c 65 73 2f 43 68 61 72 6c 65 73
标示字符串com/charles/Charles
#2 = Utf8     com/charles/Charles(第二个常量为utf8字符串)

其他的类型可以参考“Java 虚拟机规范(Java_SE_7)”和“深入理解 Java 虚拟机 JVM 高级特性与最佳实践”书籍。

6、类访问标志(access_flags)

访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。
image.png

7、类索引(this_class)

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

8、父类索引(super_class)

父类索引,对于类来说,super_class 的值必须为 0 或者是对 constant_pool 表中项目的一个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的 access_flag 中都不能带有 ACC_FINAL 标记。对于接口来说,它的 Class 文件的 super_class 项的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量 。如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是 java.lang.Object 类,只有它是唯一没有父类的类。

9、接口计数器(interfaces_count)

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

10、接口信息数据区(interfaces[interfaces_count])

接口表,interfaces[]数组中的每个成员的值必须是一个对 constant_pool 表中项目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必须为 CONSTANT_Class_info 类型常量,其中 0 ≤ i <interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。
image.png

flags: ACC_PUBLIC, ACC_SUPER
   #1 = Class              #2             //  com/charles/Charles
   #2 = Utf8               com/charles/Charles
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             //  com/charles/ICharles
   #6 = Utf8               com/charles/ICharles
00 21 标示类访问标示为ACC_PUBLIC, ACC_SUPER
00 01 类为com/charles/Charles
00 03 父类为java/lang/Object
00 01 实现了1个接口
00 05 接口为com/charles/ICharles

11、字段计数器(fields_count)

字段计数器,fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。 fields[]数组中每一项都是一个 field_info 结构的数据项,它用于表示该类或接口声明的类字段或者实例字段。

12、字段信息数据区(fields[fields_count])

字段表,fields[]数组中的每个成员都必须是一个 fields_info 结构的数据项,用于表示当前类或接口中某个字段的完整描述。 fields[]数组描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的部分。
其中字段访问 flag 为
image.png
image.png
image.png

#7 = Utf8               name
#8 = Utf8               Ljava/lang/String;
00 01 类有1个字段
00 02 字段的访问标示为ACC_PRIVATE
00 07 字段名为name
00 08 字段描述符为Ljava/lang/String;
00 00
没有属性信息数据区

13、方法计数器(methods_count)

方法计数器, methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。Methods[]数组中每一项都是一个 method_info 结构的数据项。

14、方法信息数据区(methods[methods_count])

方法表,methods[] 数组中的每个成员都必须是一个 method_info 结构的数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么它所对应的方法体就应当可以被 Java 虚拟机直接从当前类加载,而不需要引用其它类。 method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法方法和类或接口初始化方法方法 。methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
image.png
image.png

该类有4个方法,以第2个方法say()为例
00 01 00 12 00 0a 00 01 00 0b
00 01
flags: ACC_PUBLIC
00 12
#18 = Utf8               say
00 0a
#10 = Utf8               ()V
00 01
1个attriute_info属性
00 0b
#11 = Utf8               Code
Code属性

15、属性计数器(attributes_count)

属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。attributes 表中每一项都是一个 attribute_info 结构的数据项。

16、属性信息数据区(attributes[attributes_count])

java class 文件内部属性信息,和 java 语言定义的属性没有关系,纯粹就是给 java 虚拟机用的。属性表,attributes 表的每个项的值必须是 attribute_info 结构。
image.png
image.png
以第 2 个方法 say()的 code 属性为例
image.png
image.png

00 0b
#11 = Utf8               Code
00 00 00 37
代码属性区的长度为3*16 + 7
00 02
操作数栈最大深度为2
00 01
最大的局部变量数为1
00 00 00 09
方法代码的长度为9
b2 00 13 12 19 b6 00 1b b1
0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #25                 // String charles
5: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
00 00
没有异常表
00 02
2个Code属性区的属性
00 0e
行号表属性
#14 = Utf8               LineNumberTable
00 0f
本地变量表属性
#15 = Utf8               LocalVariableTable

LineNumberTable 属性
image.png
image.png

LineNumberTable:
  line 18: 0
  line 19: 8

LocalVariableTable 属性
image.png
image.png

LocalVariableTable:
  Start  Length  Slot  Name   Signature
  0       9     0    this     Lcom/charles/Charles;

在 Java 7 规范里,Class 文件结构中的 attributes 表的项包括下列定义的属性: InnerClasses 、 EnclosingMethod、Synthetic、Signature、SourceFile,SourceDebugExtension 、Deprecated、RuntimeVisibleAntations、RuntimeInvisibleAntations 以及 BootstrapMethods 属性。
对于支持 Class 文件格式版本号为 49.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的 Signature、RuntimeVisibleAntations 和 RuntimeInvisibleAntations 属性。对于支持 Class 文件格式版本号为 51.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的 BootstrapMethods 属性。Java 7 规范 要求任一 Java 虚拟机实现可以自动忽略 Class 文件的 attributes 表中的若干 (甚至全部) 它不可识别的属性项。任何本规范未定义的属性不能影响 Class 文件的语义,只能提供附加的描述信息 。
image.png
image.png
使用 java -verbose Charles 反编译,查看类信息

Last modified Oct 21, 2014; size 743 bytes
  MD5 checksum 98d8dab9aaaeeff70b2e4e77ebfedc53
  Compiled from "Charles.java"
public class com.charles.Charles implements com.charles.ICharles
  SourceFile: "Charles.java"
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             //  com/charles/Charles
   #2 = Utf8               com/charles/Charles
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             //  com/charles/ICharles
   #6 = Utf8               com/charles/ICharles
   #7 = Utf8               name
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Methodref          #3.#13         //  java/lang/Object."<init>":()V
  #13 = NameAndType        #9:#10         //  "<init>":()V
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/charles/Charles;
  #18 = Utf8               say
  #19 = Fieldref           #20.#22        //  java/lang/System.out:Ljava/io/PrintStream;
  #20 = Class              #21            //  java/lang/System
  #21 = Utf8               java/lang/System
  #22 = NameAndType        #23:#24        //  out:Ljava/io/PrintStream;
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = String             #26            //  charles
  #26 = Utf8               charles
  #27 = Methodref          #28.#30        //  java/io/PrintStream.println:(Ljava/lang/String;)V
  #28 = Class              #29            //  java/io/PrintStream
  #29 = Utf8               java/io/PrintStream
  #30 = NameAndType        #31:#32        //  println:(Ljava/lang/String;)V
  #31 = Utf8               println
  #32 = Utf8               (Ljava/lang/String;)V
  #33 = Utf8               getName
  #34 = Utf8               ()Ljava/lang/String;
  #35 = Fieldref           #1.#36         //  com/charles/Charles.name:Ljava/lang/String;
  #36 = NameAndType        #7:#8          //  name:Ljava/lang/String;
  #37 = Utf8               setName
  #38 = Utf8               SourceFile
  #39 = Utf8               Charles.java
{
  public com.charles.Charles();
    flags: ACC_PUBLIC
    Code:
      stack#1, locals#1, args_size#1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          0       5     0  this   Lcom/charles/Charles;

  public void say();
    flags: ACC_PUBLIC
    Code:
      stack#2, locals#1, args_size#1
         0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #25                 // String charles
         5: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 18: 0
        line 19: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          0       9     0  this   Lcom/charles/Charles;

  public java.lang.String getName();
    flags: ACC_PUBLIC
    Code:
      stack#1, locals#1, args_size#1
         0: aload_0
         1: getfield      #35                 // Field name:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 22: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          0       5     0  this   Lcom/charles/Charles;

  public void setName(java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack#2, locals#2, args_size#2
         0: aload_0
         1: aload_1
         2: putfield      #35                 // Field name:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 26: 0
        line 27: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          0       6     0  this   Lcom/charles/Charles;
          0       6     1  name   Ljava/lang/String;
}
https://alicharles.oss-cn-hangzhou.aliyuncs.com/static/images/mp_qrcode.jpg
文章目录
  1. Java Class 文件结构
    1. 1、 魔数(magic)
    2. 2、 版本号(minor_version, major_version)
    3. 3、常量池计数器(constant_pool_count)
    4. 4、常量池数据区(constant_pool[contstant_pool_count-1])
    5. 6、类访问标志(access_flags)
    6. 7、类索引(this_class)
    7. 8、父类索引(super_class)
    8. 9、接口计数器(interfaces_count)
    9. 10、接口信息数据区(interfaces[interfaces_count])
    10. 11、字段计数器(fields_count)
    11. 12、字段信息数据区(fields[fields_count])
    12. 13、方法计数器(methods_count)
    13. 14、方法信息数据区(methods[methods_count])
    14. 15、属性计数器(attributes_count)
    15. 16、属性信息数据区(attributes[attributes_count])