Advanced Java-05-class文件结构

class文件结构概述

class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。 我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述。
class文件中的每个数据项都有它的固定长度, 数据项的不同长度分别用u1,u2,u4,u8表示,长度分别是byte、short、int、long。
class文件中存在以下数据项(该图表参考自《深入Java虚拟机》):

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attribute_count 1
attribute_info attributes attributes_count

class文件每个区域的说明:

  • magicversion: magic 也即魔数(固定值0xCAFEBABE)占用4字节, class 文件版本号占用4字节, 不同版本的 javac 编译器编译的 class 文件, 版本号可能不同;

  • 常量池数量 constant_pool_count, 等于实际常量池中项目的数量+1,因为常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”。

  • 常量池(constant_pool)存储的内容主要包括 符号引用字面量;

  • access_flag, 在常量池之后的两个字节, 这个标志用于识别一些类或接口层次的访问信息

  • this_class/super_class/interfaces: 类索引(this_class)和父类索引(super_class)都是一个 u2 类型的数据,而接口索引集合(interfaces)则是一组 u2 类型的数据集合, Class 文件中由这三项数据来确定这个类的继承关系;

  • field_info字段表

  • method_info方法表

  • attribute_info属性表

  • 方法字节码…

class文件常量池

常量池(constant_pool)存储的内容主要包括 符号引用字面量:

  • 字面量: 主要包括字符串常量和 final 常量值;
  • 符号引用: 包括类继承的超类, 接口的全限定名, 及描述符(包括 fields 的名称和描述符, methods 的名称及描述符)
    • 类和接口的全限定名: 例如一个类的权限定名是 org/kshan/corej/TestClass;
    • 字段的名称和描述符:
      • 字段名称: 当类被加载后的链接阶段, 这些符号引用被替换为直接引用;
      • 字段描述符: 用来描述字段的类型比如二维数组 int [][] 被记录为 [[I, String[] 被记录为 [Ljava/lang/String;
    • 方法的名称和描述符:
      • 方法名称: 当类被加载后的链接阶段, 这些符号引用被替换为直接引用;
      • 方法描述符: 用来描述方法的形参/返回值, 例如方法int getIndex(String name,char[] tgc,int start,int end,char target)的描述符为(Ljava/lang/String[CIIC) I;

class 文件中的很多其他部分都是对常量池中的数据项的引用,比如后面要讲到的 this_class, super_class, field_info, attribute_info 等,另外字节码指令中也存在对常量池的引用,这个对常量池的引用当做字节码指令的一个操作数。此外,常量池中各个项也会相互引用。

注意不要与JVM内存模型中的”运行时常量池”混淆, Class文件中常量池主要存储了字面量以及符号引用,其中
字面量主要包括字符串,final 常量的值或者某个属性的初始值等等,

下图参考自: 《Java虚拟机原理图解》 Class文件中的常量池详解 @ref

实例分析 class 文件常量池

Java测试类:

public class CJEntry extends CJBaseClass implements Serializable {
public final static int thatIsConstVar = 5; // 整形常量
public static boolean thatIsStaticVar = true; // 静态
public int thatIsInstanceVar; // 实例变量
public int thatIsInstanceMethod(String input) { // 实例方法
return Integer.parseInt(input); // 调用静态方法
}
}

编译后使用 javap 分析 class 文件: javac org/mk/corej/CJBaseClass.java && javap -v org.mk.corej.CJBaseClass, 只截取输出的”Constant pool” 部分:

#1 = Methodref          #5.#23         // org/kshan/corej/CJBaseClass."<init>":()V  ## 构造方法的符号引用
#2 = Methodref #24.#25 // java/lang/Integer.parseInt:(Ljava/lang/String;)I ## Integer.parseInt()的符号引用
#3 = Fieldref #4.#26 // org/kshan/corej/CJEntry.thatIsStaticVar:Z ## 静态field的符号引用
#4 = Class #27 // org/kshan/corej/CJEntry ## 父类符号引用
#5 = Class #28 // org/kshan/corej/CJBaseClass ## 接口符号引用
#7 = Utf8 thatIsConstVar ##static final常量名字
#10 = Integer 5 ## static final常量值
#11 = Utf8 thatIsStaticVar ## 变量名字
#13 = Utf8 thatIsInstanceVar ## 变量名字
#14 = Utf8 <init> ##
#15 = Utf8 ()V ##
#18 = Utf8 thatIsInstanceMethod
#19 = Utf8 (Ljava/lang/String;)I
#23 = NameAndType #14:#15 // "<init>":()V ## 构造方法的NameAndType, <init>是构造方法的名字, ()V表示无参返回Void
#24 = Class #30 // java/lang/Integer
#25 = NameAndType #31:#19 // parseInt:(Ljava/lang/String;)I ## 静态方法名字:描述符`(形参列表)返回值`的格式
#26 = NameAndType #11:#12 // thatIsStaticVar:Z
#27 = Utf8 org/kshan/corej/CJEntry
#28 = Utf8 org/kshan/corej/CJBaseClass
#29 = Utf8 java/io/Serializable
#30 = Utf8 java/lang/Integer
#31 = Utf8 parseInt

@ref: 实例分析Java Class的文件结构 | | 酷 壳 - CoolShell

局部变量表、操作数栈

@todo