[JVM] Class 类文件结构

[JVM] Class 类文件结构

------<深入理解Java虚拟机 第3版> 第三部分第6章 读书笔记

1. 无关性的基石

Java技术发展之处,就曾考虑并实现了让其他语言运行在Java虚拟机上的可能性。规范文档的发布,也可以把Java的规范拆分成《Java语言规范》和《Java虚拟机规范》两部分。现如今,商业企业和开源机构已经发展处一大批如Kotlin、Coljure、Groovy、JRuby、JPython、Scala等可以运行在Java虚拟机之上的语言。

作为一个通用的、与机器无关的执行平台,任何其他语言的实现都可以将JVM作为代码编译的运行基础,以Class文件作为他们的交付媒介。例如Java编译器可以将Java代码编译成存储字节码的class文件,使用JRuby等其他语言的编译器一样可以将源代码编辑成class文件。JVM不关心class文件的源代码是何种语言。

2. 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];
}
2.1 类文件的两种数据类型
  • 无符号数: 基本数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数

  • 表:多个无符号数组成的复合数据类型,通常以”_info“结尾。表用于描述有层次关系的复合结构的数据

2.2 Magic Number 和Class文件版本

每个class文件的头四个字节被称为魔法数字(Magic Number),它的唯一作用是确定这个文件是否能被虚拟机接受。Class文件的魔数,0xCAFEBABE(咖啡宝贝?)这个魔数在Java还被称为“Oak”. 见图1.


图1. magic number

紧接着魔法数字后面的4个字节为Class文件的版本号:第5和第6字节Minor Version次版本号,第7和第8字节为Major Version主版本号。如图2, 0x0034, 即十进制的52,说明这个是JDK8的Class文件。

图2
图2. 版本号

表1. JDK版本号对应表

JDK版本-source参数版本号
JDK 61.1~650.0
JDK 71.1~751.0
JDK 81.1~852.0
JDK 96~953.0
JDK 106~1054.0
JDK 116~1155.0
JDK 126~1256.0
JDK 136~1357.0
2.3 常量池

紧接着版本号之后,是常量池入口。常量池是Class文件里的资源仓库,是Class文件结构中与其他项目关联最多的数据。长度不固定,所以入口放置一个u2类型的容量技术。常量池的容量计数是从1开始的。

例如图3, 0x0030即十进制的48,说明该class文件中含有48个常量,#1~#47
图3
图4. 常量池长度

用Javap反编译可以印证1
图4
图5. javap反编译

常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference),字面量比较接近Java语言层面的常量概念,比如文本字符串,被生命为final的常量值等。符号引用包括:

  • 被模块导出或者开放的包(package)
  • 类和接口的全限定名(Fully Qualified Name) (包名+类名,比如java.lang.String)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符
  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  • 动态调用点和动态常量(Dynamically-computed Call Site、Dynamicaaly-computed Constant)

表2: 常量类型与标志位对应表

类型标志描述
CONSTANT_Uft8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或者接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或者方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16表示方法类型
CONSTANT_Dynamic_info17表示一个动态计算常量
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
CONSTANT_Module_info19表示一个模块
CONSTAN_Package_info20表示一个模块中开放或者导出的包

继续之前的栗子,如图5,标识常量池长度的0x0030之后,0x0A, 标志=10,对应上表可发现,常量池中#1 是Methodref.

CONSTANT_Methodref_info {
    //The tag item of a CONSTANT_Methodref_info structure has the value CONSTANT_Methodref (10).
    u1 tag; 
    
    //The class_index item of a CONSTANT_Methodref_info structure must be a class type, not an interface type.
    u2 class_index; 
    
    //The value of the name_and_type_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_NameAndType_info structure (§4.4.6). This constant_pool entry indicates the name and descriptor of the field or method.
    u2 name_and_type_index;
}

结合Methodref的结构来看,可知Class_index是0x0008即#8, NameAndType的index是0x001B, 即#27


图6. 常量池#1详解

图7
图7. javap反编译

在这需要说明一点的是,CONSTANT_Uft8_info的解码。CONSTANT_Uft8_info的结构如下:

//CONSTANT_Uft8_info
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

以常量池#9为例2.

在这里插入图片描述
图8. UTF8解码

CONSTANT_Uft8_info中length的最大长度为65535, 故而如果Java程序中定义了超过64KB英文字符的变量或者方法名,是无法编译的。

常量池完整的17种数据类型的结构参见:docs.oracle

2.4 访问标志

在常量池结束之后,紧接着2个字节代表方位标志(access_flags), 这个标志用于识别一些类或者接口的访问信息。

表3. 访问标志

标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指定的新语义。JDK1.2之后编译出来的类的这个标志都必须为真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类此标识为真
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举
ACC_MODULE0x8000标识这是一个模块

例子中的类是个public的普通类,ACC_PUBLIC与ACC_SUPER为真, 0x0001|0x0020 =0x0021
在这里插入图片描述
图9. 访问标志

2.5 类索引、父类索引和接口索引集合

类索引(this_class)和父类索引(super_class)均问u2类型的数据,接口索引为u2类型的数据的集合。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名(除了java.lang.Object之外,父类索引均不为0)。接口索引为了确定这个类实现了哪些接口。(如果这是个class,则按implements后接口顺序,如果这是个interface,则按extends后的接口顺序)
在这里插入图片描述
图10

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210626141227659.png
接口索引的结构,入口为u2类型的接口计数器,表明接口索引表的容量,如没有实现接口则为0
在这里插入图片描述
图11

在这里插入图片描述
图12

2.6 字段表集合
field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;//
    attribute_info attributes[attributes_count];
}
  • access_flag:访问标志,包括字段修饰符(public、private、protected等),实例变量还是类变量(static),可变性(final),并发可见性(volatic),可否被序列化(transient)等
  • name_index: 字段名称在常量池中的index
  • descriptor_index: 字段描述符在常量池中的index,用来描述字段的数据类型
  • attributes_count: 属性表集合的容量
  • attribute_info: 见2.8节

表4.access_flags 字段访问标志

Flag NameValueInterpretation
ACC_PUBLIC0x0001Declared public; may be accessed from outside its package.
ACC_PRIVATE0x0002Declared private; usable only within the defining class.
ACC_PROTECTED0x0004Declared protected; may be accessed within subclasses.
ACC_STATIC0x0008Declared static.
ACC_FINAL0x0010Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE0x0040Declared volatile; cannot be cached.
ACC_TRANSIENT0x0080Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC0x1000Declared synthetic; not present in the source code.
ACC_ENUM0x4000Declared as an element of an enum.

表5. descriptor 字段描述符

FieldType termTypeInterpretation
Bbytesigned byte
CcharUnicode character code point in the Basic Multilingual Plane, encoded with UTF-16
Ddoubledouble-precision floating-point value
Ffloatsingle-precision floating-point value
Iintinteger
Jlonglong integer
LClassName/referencean instance of class ClassName
Sshortsigned short
Zbooleantrue or false
[referenceone array dimension(二维数据记为"[[]]")
Vvoidvoid
2.7 方法表集合
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • access_flag: 方法的访问标志,见表6
  • name_index: 方法名的常量池索引
  • descriptor_index: 方法的描述符索引,与字段描述符不同的是,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号中。 例如:int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int gargetCount, int fromIndex) 的描述符为 ([CII[CIII)I

方法中的具体行为,经过javac的编译成字节码后,存放在attribute_info中的code里。详见2.8 属性表集合。

表6. 方法的访问标志

Flag NameValueInterpretation
ACC_PUBLIC0x0001Declared public; may be accessed from outside its package.
ACC_PRIVATE0x0002Declared private; accessible only within the defining class.
ACC_PROTECTED0x0004Declared protected; may be accessed within subclasses.
ACC_STATIC0x0008Declared static.
ACC_FINAL0x0010Declared final; must not be overridden
ACC_SYNCHRONIZED0x0020Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE0x0040A bridge method, generated by the compiler.
ACC_VARARGS0x0080Declared with variable number of arguments.
ACC_NATIVE0x0100Declared native; implemented in a language other than Java.
ACC_ABSTRACT0x0400Declared abstract; no implementation is provided.
ACC_STRICT0x0800Declared strictfp; floating-point mode is FP-strict.
ACC_SYNTHETIC0x1000Declared synthetic; not present in the source code.
2.8 属性表集合
attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

  • attribute_name_index: 属性名在常量池中的index
  • attribute_length: 属性表不包括attribute_name_index和attribute_length的长度

在Java SE 12中,预定义的属性已经增加到了29个。见表6. 下文介绍其中关键的常用的属性。

Attributeclass fileJava SESection
ConstantValue45.31.0.2§4.7.2
Code45.31.0.2§4.7.3
Exceptions45.31.0.2§4.7.5
SourceFile45.31.0.2§4.7.10
LineNumberTable45.31.0.2§4.7.12
LocalVariableTable45.31.0.2§4.7.13
InnerClasses45.31.1§4.7.6
Synthetic45.31.1§4.7.8
Deprecated45.31.1§4.7.15
EnclosingMethod49.05.0§4.7.7
Signature49.05.0§4.7.9
SourceDebugExtension49.05.0§4.7.11
LocalVariableTypeTable49.05.0§4.7.14
RuntimeVisibleAnnotations49.05.0§4.7.16
RuntimeInvisibleAnnotations49.05.0§4.7.17
RuntimeVisibleParameterAnnotations49.05.0§4.7.18
RuntimeInvisibleParameterAnnotations49.05.0§4.7.19
AnnotationDefault49.05.0§4.7.22
StackMapTable50.06§4.7.4
BootstrapMethods51.07§4.7.23
RuntimeVisibleTypeAnnotations52.08§4.7.20
RuntimeInvisibleTypeAnnotations52.08§4.7.21
MethodParameters52.08§4.7.24
Module53.09§4.7.25
ModulePackages53.09§4.7.26
ModuleMainClass53.09§4.7.27
NestHost55.011§4.7.28
NestMembers55.011§4.7.29
Record60.016§4.7.30

红色标注:5个对JVM能正确解读class文件至关重要的属性

蓝色标注:12个对Java SE类库能正确解读class文件至关重要的属性

2.8.1 Code属性

Code属性是Class文件中最重要的一个属性,用户描述代码指令,所有的其他数据项目都用于描述元数据。

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

  1. javap官方文档 ↩︎

  2. [UTF8 codepage]UTF-8 codepage ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值