04 class文件格式

The Java Virtual Machine Specification, Java SE 8

本文介绍了class文件格式、用于表示已编译的类和接口的硬件和操作系统相关的二进制格式。

目录

介绍

一、ClassFile结构(The ClassFile Structure)

二、名称的内部形式(The Internal Form of Names)

2.1 二进制类和接口名称

2.2 非限定名称

三、描述符(Descriptors)

3.1 语法符号(Grammar Notation)

3.2 字段描述符(Field Descriptors)

3.3 方法描述符(Method Descriptors)

四、常量池(The Constant Pool)

五、字段(Fields)

六、方法(Methods)

七、属性(Attributes)

八、格式化检查(Format Checking)

九、对Java虚拟机代码的约束(Constraints on Java Virtual Machine Code)

十、class文件的验证(Verification of class Files)

十一、Java虚拟机的限制(Limitations of the Java Virtual Machine)


介绍

本章介绍了Java虚拟机的 class文件格式。每个 class文件都包含单个类或接口的定义。尽管类或接口不需要有一个真正包含在文件中的外部表示(例如,因为类是由类加载器生成的),但我们将把类或接口的任何有效表示称为 class文件格式。

一个class文件由一个8位字节的流组成。所有16位、32位和64位的数目都是通过分别通过读取2、4和8个连续的8位字节来构造的。多字节数据项总是以大端顺序存储,其中高字节先出现。
在Java SE平台中,接口 java.io.DataInput和 java.io.DataOutput 和 类似 java.io.DataInputStream 和 java.io.DataOutputStream的classes类都支持这种格式。

本章定义了表示 class文件数据的数据类型集:类型u1、u2和u4分别表示无符号的一字节、二字节或四字节的数量。在Java SE平台中,这些类型可以通过类似 readUnsignedByte、readUnsignedShort 和 java.io.DataInput接口的 readInt方法读取。

本章介绍了使用类C结构符号编写的伪结构的 class文件格式。为了避免与类和类实例等字段的混淆,描述 class文件格式的结构的内容被称为项 items。连续的项按顺序存储在 class文件中,没有填充或对齐。

Tables由零个或多个可变大小的项组成,在多个 class文件结构中使用。虽然我们使用类似C的数组语法来引用表项,但表是不同大小的结构流,这意味着不可能将表索引直接转换为字节偏移量到表中。

我们将数据结构称为 array数组,它由0个或多个连续的固定大小的项目组成,可以像数组一样进行索引。

本章中对ASCII字符的引用应解释为与ASCII字符对应的Unicode代码点。

一、ClassFile结构(The ClassFile Structure)

class文件由一个 ClassFile结构组成:

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

ClassFile 结构中的项如下:

magic

        magic项提供标识 class文件格式的 magic数字;它的值为0xCAFEBABE。

minor_version, major_version

        minor_version 和 major_version项的值是这个 class文件的次要版本号和主要版本号。
        主版本号和次要版本号一起决定了 class文件格式的版本。
        如果一个 class文件有主版本号M和小版本号M,我们将其 class文件格式的版本表示为M.m。
        因此,class文件格式版本可以按字典顺序排序,例如,1.5<2.0<2.1。

        Java虚拟机实现可以支持版本v的类文件格式,当且仅当v位于某个连续范围Mi.0 ≤ v ≤ Mj.m.
        Java虚拟机实现所符合的Java SE平台的发布级别负责确定其范围。

Oracle在JDK1.0.2版本中的Java虚拟机实现支持 class文件格式版本45.0到45.3。
JDK发布了1.1.*支持包含45.0到45.65535范围内的 class文件格式版本。
对于k≥2,JDK版本1.k支持包含45.0到44+k.0范围内的 class文件格式版本。

constant_pool_count 常量池计数

        constant_pool_count项的值等于 constant_pool表中的条目数加上1。
        如果 constant_pool索引大于0且小于constant_pool_count,则认为它有效,但4.4.5中指出的long型和double型常数除外。

constant_pool[] 常量池数组

        constand_pool是一个结构表(4.4),表示各种类字符串常量、类和接口名、字段名称以及ClassFile结构及其子结构中引用的其他常量。每个constant_pool表项的格式由它的第一个“标记”字节表示。

        constant_pool表被索引为constant_pool_count - 1。

access_flags 修饰符

        access_flags项的值是用于表示对此类或接口的访问权限和属性的标志的掩码。
        每个标志的解释如表4.1-A所示。

Table 4.1-A. 类访问和属性修改符

标志名称
解释说明
ACC_PUBLIC0x0001声明为public;可以从其包外部访问。
ACC_FINAL0x0010声明为final;不允许子类。
ACC_SUPER0x0020特别是在被调用的特殊指令调用时,处理超类方法。
ACC_INTERFACE0x0200是一个接口,而不是一个类。
ACC_ABSTRACT0x0400声明为abstract;不能被实例化。
ACC_SYNTHETIC0x1000声明为 synthetic合成;在源代码中不存在。
ACC_ANNOTATION0x2000声明为 annotation注解类型。
ACC_ENUM0x4000声明为 enum类型

接口通过设置ACC_INTERFACE标志来区分。
如果没有设置ACC_INTERFACE标志,则此 class文件定义了一个类,而不是一个接口。

如果设置了ACC_INTERFACE标志,则还必须设置ACC_ABSTRACT标志,并且不能设置ACC_FINAL、ACC_SUPER和ACC_ENUM标志。

如果未设置ACC_INTERFACE标志,则可以设置除ACC_ANNOTATION之外的表4.1-A可能设置除了ACC_ANNOTATION。但是,这样的类文件不能同时设置其ACC_FINAL和ACC_ABSTRACT标志(JLS 8.1.1.2)。

ACC_SUPER标志表示如果这个 invokespecial特殊指令出现在这个类或接口中,则表示两个备选语义中的哪一种。对Java虚拟机的指令集的编译器应该设置ACC_SUPER标志。在Java SE 8及更高版本中,Java虚拟机认为要在每个 class文件中都设置ACC_SUPER标志,而不管类文件中该标志的实际值和 class文件的版本如何。

ACC_SUPER标志的存在是为了向后与由Java编程语言的旧编译器编译的代码兼容。
在1.0.2之前的JDK版本中,编译器生成了access_flag,其中现在表示ACC_SUPER的标志没有指定的意义,如果Oracle的Java虚拟机实现设置了该标志,则会忽略它。

ACC_SYNTHETIC标志表示此类或接口是由编译器生成的,并且不会出现在源代码中。

注释类型必须已设置了其ACC_ANNOTATION标志。
如果设置了ACC_ANNOTATION标志,则还必须设置ACC_INTERFACE标志。

ACC_ENUM标志表示此类或其超类被声明为枚举类型。

表4.1-A中未分配的access_flags项的所有位都被保留以供将来使用。
在生成的 class文件中,它们应该被设置为零,并且应该被Java虚拟机实现所忽略。

this_class

        this_class项的值必须是常量_pool表中的有效索引。
        该索引处的 costant_pool条目必须是CONSTANT_Class_info结构(4.4.1),表示该 class文件定义的类或接口。

super_class

        对于一个类,超级类项的值必须为零,或者必须是constant_pool表中的有效索引。
        如果 super_class项的值为非零,则该索引上的 constant_pool条目必须是一个CONSTANT_Class_info结构,表示由该类文件定义的类的直接超类。
       直接超类和它的任何超类都不能在其ClassFile结构的access_flags项中设置ACC_FINAL标志。
        如果super_class项的值为零,则 this类文件必须表示类object,这是唯一没有直接超类的类或接口。

        对于接口,super_class项的值必须始终是constant_pool表中的有效索引。
        该索引处的 constant_pool条目必须是表示类 Object的CONSTANT_Class_info结构。

interfaces_count

        interfaces_count项的值给出了这个类或接口类型的直接超接口的数量。

interfaces[]

        interfaces数组中的每个值都必须是 constant_pool表中的有效索引。
        每个 interfaces[i] 处的常数池条目,其中0≤i<interfaces_count,必须是一个CONSTANT_Class_info结构,表示该类或接口类型的直接超接口,按该类型的源代码中给出的从左到右的顺序排列。

fields_count

        字段 field_count项的值给出了字段表中 field_info结构的数量。
        field_info结构表示由此类或接口类型声明的所有字段,包括类变量和实例变量。

fields[]

        fileds表中的每个值都必须是一个field_info结构(4.5),它给出了此类或接口中的一个字段的完整描述。
        fileds表中只包含由此类或接口声明的那些字段。它不包括表示从超类或超接口继承的字段的项。

methods_count

        methods_count项的值给出了 methods表中methoc_info结构的数量。

methods[]

        methods表中的每个值都必须是method_info结构,(4.6)给出此类或接口中方法的完整描述。

        如果在 method_info结构的 access_flags项中没有设置ACC_NATIVE和ACC_ABSTRACT标志,那么也会提供实现该方法的Java虚拟机指令。

        method_info结构表示由此类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法(2.9),以及任何类或接口初始化方法(2.9)。
        methods表不包括表示从超类或超接口继承的方法的项。

attributes_count

        attributes_count项的值表示此类的属性表中的属性数量。

attributes[]

        attributes表中的每个值都必须是一个 attributes_info结构(4.7)。

        表4.7-C 中列出了由此规范定义的属性,它们出现在 ClassFile结构的属性表中。

        关于要出现在 ClassFile结构的属性表中所定义的 attributes的规则在4.7中给出。

        ClassFile结构的 attributes表中给出了与非预定义属性的相关的规则。

二、名称的内部形式(The Internal Form of Names)

2.1 二进制类和接口名称

出现在 class文件结构中的类名称和接口名称总是以一种完全限定的形式表示,称为二进制名称(JLS 13.1)。这些名称总是表示为CONSTANT_Utf8_info结构(4.4.7),因此可以从整个Unicode代码空间中绘制,而不受进一步的约束。类和接口名称引用自那些CONSTANT_NameAndType_info结构(4.4.6),这些结构属于描述符(4.3),以及所有CONSTANT_Class_info结构(4.4.1)。

由于历史原因,出现在类文件结构中的二进制名称的语法与JLS 13.1中记录的二进制名称的语法不同。在这种内部形式中,ASCII周期(.)通常分隔组成二进制名称的标识符会被ASCII向前斜杠(/)取代。标识符本身必须是非限定(unqualified)的名称(4.2.2)。

例如,Thread类的普通二进制名称是java.lang.Thread。
在类文件格式的描述符中使用的内部形式中,对类线程名称的引用是使用表示字符串java/lang/Thread的CONSTANT_Utf8_info结构实现的。

2.2 非限定名称

方法、字段、局部变量和形式参数的名称存储为非限定名称。非限定的名称必须包含至少一个Unicode代码点,并且不能包含任何ASCII字符 . ;[ / (即句点或分号或左方括号或正斜杠)。

方法名称被进一步约束,因此除了特殊的方法名<init>和<clinit>(2.9)外,它们不能包含ASCII字符<或>(即左角括号或右角括号)。

注意,字段名或接口方法名可以是<init>或<client>,但是没有方法调用指令可以引用<client>,只有 invokespecial 指令可以引用<init>。

三、描述符(Descriptors)

描述符是表示字段或方法的类型的字符串。描述符使用修改后的UTF-8字符串(4.4.7)以类文件格式表示,因此可以从整个Unicode代码空间中绘制,而不受进一步的约束。

3.1 语法符号(Grammar Notation)

描述符使用语法指定的。语法是一组产品,它描述了字符序列如何形成语法上正确的各种描述符。语法的终端符号以固定宽度的字体显示。非终端符号以斜体类型表示。非终端的定义由被定义的非终端的名称引入,后面是冒号。一个或多个非终端的替代定义,然后遵循后续的行。

产品右侧的语法{x}表示x。

产品右侧的短语(one of)表示以下一行或几行上的每个终端符号都是另一种定义。

3.2 字段描述符(Field Descriptors)

字段描述符表示类、实例或局部变量的类型。

FieldDescriptor:
        FieldType


FieldType:
        BaseType
        ObjectType
        ArrayType


BaseType:
        (one of)
        B C D F I J S Z

ObjectType:
        L ClassName ;

ArrayType:
        [ ComponentType

ComponentType:
        FieldType

BaseType的字符,ObjectType的 L 和 ; ,以及ArrayType的 [ 都是ASCII字符。

ClassName表示以内部形式(4.2.1)编码的二进制类或接口名称。

字段描述符作为类型的解释如表4.3-A所示。

表示数组类型的字段描述符仅在表示具有255个或更少维度的类型时才有效。

Table 4.3-A. 字段描述符的解释

字段类型术语 FieldType term类型Type解释说明
Bbyte带符号字节
Cchar基本多语言平面中的Unicode字符代码点,用UTF-16编码
Ddouble双精度浮点值
Ffloat单精度浮点值
Iintn. 整数
Jlong长整数
LreferenceClassName的一个实例
S  ClassName ;short有符号短整型
Zbooleantrue 或 false
[reference一个数组维度

具有 int类型的实例变量的字段描述符仅为 I

Object类型的实例变量的字段描述符是Ljava/lang/Object;
请注意,我们使用了Object类的二进制名称的内部形式。

多维数组类型 double[] [] [] 的实例变量的字段描述符为 [ [ [ D。

3.3 方法描述符(Method Descriptors)

方法描述符包含0个或多个参数描述符,表示方法所接受的参数的类型,以及一个返回描述符,表示方法返回的值的类型(如果有的话)。

MethodDescriptor:
        ( {ParameterDescriptor} ) ReturnDescriptor

ParameterDescriptor:
        FieldType

ReturnDescriptor:
        FieldType
        VoidDescriptor

VoidDescriptor:
        V

字符V表示该方法不返回任何值(其结果无效)

该方法的方法描述符:

Object m(int i, double d, Thread t) {...}

(IDLjava/lang/Thread;)Ljava/lang/Object;

请注意,我们使用了Thread和Object的二进制名称的内部形式。

方法描述符仅在表示总长度大于或等于255的方法参数时有效,其中 this长度包括在实例或接口方法调用时对该参数的贡献。总长度是通过对单个参数的贡献相加来计算的,其中一个类型为long或double的参数对长度贡献两个单位,而任何其他类型的参数贡献一个单位。

方法描述符与它所描述的方法是类方法还是实例方法都是相同的。尽管这样传递了一个实例方法,但是对正在调用 this方法的对象的引用,除了其预期的参数之外,这一事实并没有反映在方法描述符中。对 this的引用是由调用实例方法的Java虚拟机指令(2.6.1,4.11)。

四、常量池(The Constant Pool)

Java虚拟机指令不依赖于类,接口,类实例或数组的运行时布局。相反,语句引用constant_pool表中的符号信息。

所有constant_pool表项都具有以下通用格式:

cp_info {
    u1 tag;
    u1 info[];
}

constant_pool表中的每个项目必须以1字节标记开头,指示cp_info条目的类型。
info 数组的内容随 tag的值而变化。
表4.4-A中列出了有效的标记及其值。每个标记字节后面必须有两个或多个字节,以给出有关特定常量的信息。附加信息的格式随标签值的不同而不同。

Table 4.4-A. 常量池标签
常量类型
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
CONSTANT_MethodHandle
15
CONSTANT_MethodType
16
CONSTANT_InvokeDynamic
18

4.1 CONSTANT_Class_info结构

CONSTANT_Class_info 结构用于表示一个类或一个接口:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

CONSTANT_Class_info结构的各项如下:

tag
       tag项具有CONSTANT_Class(7)值
name_index
        name_index项的值必须是constant_pool表中的有效索引。该索引处的costant_pool项必须是CONSTANT_Utf8_info结构(4.4.7),表示以内部形式编码的有效二进制类或接口名称。
        因为数组是对象,因为操作码  anewarraymultianewarray——而不是 new——可以通过constant_pool表中的CONSTANT_Class_info结构引用数组“类”。对于这样的数组类,该类的名称是数组类型的描述符。
例如,表示二维数组类型int[][]的类名为[[I],而表示Thread[]类型的类名为[Ljava/lang/Thread; .

数组类型描述符只有在表示255个或更少的维度时才有效。

4.2 CONSTANT_Fieldref_info, CONSTANT_Methodref_info,和CONSTANT_InterfaceMethodref_info结构

字段、方法和接口方法都用类似的结构来表示:

CONSTANT_Fieldref_info {
 u1 tag;
 u2 class_index;
 u2 name_and_type_index;
}
CONSTANT_Methodref_info {
 u1 tag;
 u2 class_index;
 u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
 u1 tag;
 u2 class_index;
 u2 name_and_type_index;
}

tag
        CONSTANT_Fieldref_info结构的标记项的值为CONSTANT_Fieldref(9)。
        CONSTANT_Methodref_info结构的标记项的值为CONSTANT_Methodref(10)。
        CONSTANT_InterfaceMethodref_info结构的标记项的值为CONSTANT_InterfaceMethodref(11)。

class_index
        class_index项的值必须是constant_pool表中的有效索引。该索引处的costant_pool项必须是CONSTANT_Class_info结构(4.4.1),表示具有字段或方法作为成员的类或接口类型。
        CONSTANT_Methodref_info结构的class_index项必须是类类型,而不是接口类型。
        CONSTANT_InterfaceMethodref_info结构的class_index项必须是接口类型。
        CONSTANT_Fieldref_info结构中的class_index项可以是类类型,也可以是接口类型。

name_and_type_index
        name_and_type_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool项必须是一个CONSTANT_NameAndType_info结构(4.4.6)。这个constant_pool条目指示字段或方法的名称和描述符。
        在CONSTANT_Fieldref_info中,所指示的描述符必须是一个字段描述符(4.3.2)。否则,指定的描述符必须是一个方法描述符(4.3.3)。
        如果CONSTANT_Methodref_info结构的方法的名称以 '<' ('\u003c') 开头,则该名称必须是特殊名称 <init>,表示一个实例初始化方法(2.9)。此种方法的返回类型必须为void。

4.3 CONSTANT_String_info结构

CONSTANT_String_info结构用于表示String类型的常量对象:

CONSTANT_String_info {
 u1 tag;
 u2 string_index;
}

CONSTANT_String_info结构的项如下:

tag
        CONSTANT_String_info结构的标记项的值为CONSTANT_String(8)。

string_index
        string_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool项必须是一个CONSTANT_Utf8_info结构(4.4.7),表示要初始化 String对象的Unicode代码点的序列。

4.4 CONSTANT_Integer_info 和 CONSTANT_Float_info 结构

CONSTANT_Integer_info和CONSTANT_Float_info结构表示4字节的数字(int和float)常量:

CONSTANT_Integer_info {
 u1 tag;
 u4 bytes;
}
CONSTANT_Float_info {
 u1 tag;
 u4 bytes;
}

这些结构的项如下:

tag
        CONSTANT_Integer_info结构的标记项的值为CONSTANT_Integer(3)。
        CONSTANT_Float_info结构的标记项的值为“CONSTANT_Float(4)”。

bytes
        CONSTANT_Integer_info结构的字节项表示int常量的值。值的字节以大单位(高字节优先)顺序存储。
        CONSTANT_Float_info结构的字节项表示IEEE 754浮点单格式(2.3.2)中的浮点常数的值。单格式表示的字节以大端(高字节优先)顺序存储。
        由CONSTANT_Float_info结构所表示的值确定如下。该值的字节首先被转换为一个int常数位。然后

  • 如果位是0x7f800000,float数值将为正无穷大。
  • 如果位是0xff800000,float数值将为负无穷大。
  • 如果位在0x7f800001到0x7fffffff范围内,或在0xff800001到0xffffffff范围内,则float值将为NaN。
  • 在所有其他情况下,让s、e和m是三个可以从位中计算出来的值:
int s = ((bits >> 31) == 0) ? 1 : -1;
int e = ((bits >> 23) & 0xff);
int m = (e == 0) ?
 (bits & 0x7fffff) << 1 :
 (bits & 0x7fffff) | 0x800000;

则浮点数值等于数学表达式s·m·2^(e-150)的结果。

五、字段(Fields)

六、方法(Methods)

七、属性(Attributes)

八、格式化检查(Format Checking)

九、对Java虚拟机代码的约束(Constraints on Java Virtual Machine Code)

十、class文件的验证(Verification of class Files)

十一、Java虚拟机的限制(Limitations of the Java Virtual Machine)

Java虚拟机的以下限制隐含在类文件格式中:

  • 通过ClassFile结构(4.1)的16位 constant_pool_count字段,每个类或每个接口的常数池被限制为65535个条目。这是对单个类或接口的总复杂性的内部限制。
  • 根据ClassFile结构(4.1)的 fields_count项的大小,类或接口可以声明的字段数量被限制为65535。
    请注意,类文件结构中的 fields_count项的值不包括从超类或超接口继承的字段。
  • 根据ClassFile结构的 methods_count项的大小,类或接口可以声明的方法的数量被限制为65535。
    注意,类文件结构的 mettucs_count项的值不包括从超类或超接口继承的方法。
  • 根据ClassFile结构的 intefaces_count项的大小,一个类或接口的直接超接口的数量被限制为65535
  • 最大数量的本地变量的本地变量数组的帧创建在调用的方法(2.6)被限制在65535的大小项目max_locals的代码属性(4.7.3)给方法的代码,和16位本地变量的Java虚拟机的索引指令集。
    请注意,long 和 double类型的值都被认为保留两个局部变量,并对max_locals值贡献两个单位,因此使用这些类型的局部变量进一步降低了这个限制。
  • 通过Code属性(4.7.3)的max_stack字段,帧(2.6)中的操作数堆栈的大小被限制为65535个值。
    请注意,长类型和双倍类型的值都被认为对max_stack值贡献了两个单位,因此在操作数堆栈上使用这些类型的值进一步降低了这个限制。
  • 根据方法描述符(4.3.3)的定义,方法参数的数量被限制为255个,其中,在实例或接口方法调用时,该this限制包括一个单位。
    请注意,方法描述符是根据方法参数长度的概念定义的,其中类型 long或 double的参数对长度贡献两个单位,因此这些类型的参数进一步降低了限制。
  • CONSTANT_Utf8_info结构的16位无符号 length项(4.4.7)将字段和方法名、字段和方法描述符以及其他常量字符串值(包括 ConstantValue(4.7.2)属性)的长度限制为65535个字符。
  • 数组中的维度数受 multianewarray指令的 dimensions操作码的大小以及 multianewarrayanewarraynewarray指令(4.9.1,4.9.2)的限制为255。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值