JVM - 类文件结构

1. Class文件数据类型

根据Java虚拟机规范规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数

无符号数

无符号数属于基本的数据类型,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。

标记 含义
u1 1个字节无符号数
u2 2个字节无符号数
u4 4个字节无符号数
u8 8个字节无符号数

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。

2. Class文件结构

每一个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];
}

2.1. 魔数

每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。

2.2. 版本号

紧接着魔数的4个字节存储的是Class文件的版本号:
第5和第6个字节是次版本号(Minor Version)
第7和第8个字节是主版本号(Major Version)

2.3. 常量池(cp_info)

常量池中包含了与文件中类和接口相关的常量。
常量池中主要存放两大类常量:字面量(Literal)符号引用(Symbolic References)

2.3.1. 字面量

文本字符串、声明为final的常量值等。

2.3.2. 符号引用

符号引用属于编译原理方面的改了,包括下面三类常量:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

全限定名
当常量池入口指向类或接口时,它们给出该类或者接口的全限定名。在class文件中,全限定名的点用斜线取代了。例如,在class文件中,java.lang.Object的全限定名表示为java/lang/Object; 在class文件中,java.util.Hashtable的全限定名表示为java/util/Hashtable。

简单名称
字段名和方法名以简单名称(非全限定名)形式出现在常量池入口。例如,一个指向类java.lang.Object所属方法String toString()的常量池入口有一个形如"toString"的方法名。一个指向类java.lang.System所属字段java.io.PrintStream out的常量池入口有一个形如“out”的字段名。

描述符
字段的描述符给出了字段的类型;方法描述符给出了方法的返回值和方法参数的数量、类型以及顺序。

基本类型字符解释表

字符 类型 含义
B byte 有符号字节型数
C char Unicode字符,UTF-16编码
D double 双精度浮点数
F float 单精度浮点数
I int 整数型
J long 长整数
S short 有符号短整数
Z boolean 布尔值 true/false
L Classname; reference 一个名为<Classname>的实例
[ reference 一个一维数组

字段描述符示例

描述符 字段声明
I int i;
[[J long[][] windingRoad;
[Ljava/lang/Object; java.lang.Object[] stuff;
Ljava/util/Hashtable; java.util.Hashtable ht;
[[[Z boolean[][][] isReady;

方法描述符示例

描述符 方法声明
( ) I int getSize();
( ) Ljava/lang/String; String toString();
( [Ljava/lang/String;) V void main (String[] args);
( ) V void wait()
(JI) V void wait (long timeout, int nanos)
(ZILjava/lang/String;II) Z boolean regionMatchs(boolean ignoreCase, int toOffset, String other, int offset, int len);
([BII) I) int read (byte[] b, int off, int len);

2.3.3. 常量池的项目类型

常量池包括以下项目:

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

2.3.3.1. CONSTANT_Utf8_info

用于表示字符串常量的值,结构如下:

CONSTANT_Utf8_info {
    u1 tag;// 值为1
    u2 length;// bytes[]数组的长度
    u1 bytes[length];
}

2.3.3.2. CONSTANT_Integer_info

CONSTANT_Integer_info {
    u1 tag;// 值为3
    u4 bytes;// 按照高位在前存储的int值
}

2.3.3.3 CONSTANT_Float_info

CONSTANT_Float_info {
    u1 tag;// 值为4
    u2 bytes;// 按照高位在前存储的float值
}

2.3.3.4. CONSTANT_Long_info

CONSTANT_Long_info {
    u1 tag;// 值为5
    u8 bytes;// 按照高位在前存储的long值
}

2.3.3.5. CONSTANT_Double_info

CONSTANT_Double_info {
    u1 tag;// 值为6
    u8 bytes;// 按照高位在前存储的double值
}

2.3.3.6. CONSTANT_Class_info

用于表示类或接口,结构如下:

CONSTANT_Class_info {
    u1 tag;// 值为7
    u2 name_index;// 包含类或者接口全限定名的CONSTANT_Utf8_info表的索引
}

2.3.3.7. CONSTANT_String_info

用于表示java.lang.String类型的常量对象,结构如下:

CONSTANT_String_info {
    u1 tag;// 值为8
    u2 string_index;
}

2.3.3.8. CONSTANT_Fieldref_info

CONSTANT_Fieldref_info {
    u1 tag;// 值为9
    u2 class_index;
    u2 name_and_type_index;
}

2.3.3.9. CONSTANT_Methodref_info

CONSTANT_Methodref_info {
    u1 tag;// 值为10
    u2 class_index;
    u2 name_and_type_index;
}

2.3.3.10. CONSTANT_InterfaceMethodref_info

CONSTANT_InterfaceMethodref_info {
    u1 tag;// 值为11
    u2 class_index;
    u2 name_and_type_index;
}

2.3.3.11. CONSTANT_NameAndType_info

用于表示字段或方法,结构如下:

CONSTANT_NameAndType_info {
    u1 tag;//值为12
    u2 name_index;
    u2 descriptor_index;
}

2.3.3.12. CONSTANT_MethodHandle_info

用于表示方法句柄,结构如下:

CONSTANT_MethodHandle_info {
    u1 tag;// 值为15
    u1 reference_kind;// 值必须在1至9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
    u2 reference_index;
}

2.3.3.13. CONSTANT_MethodType_info

用于表示方法类型,结构如下:

CONSTANT_MethodType_info {
    u1 tag;//值为16
    u2 descriptor_index;
}

2.3.3.14. CONSTANT_InvokeDynamic_info

用于表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。

CONSTANT_InvokeDynamic_info {
    u1 tag;//值为18
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}

2.4. 访问标志

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

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类的这个标志都必须为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生的
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举

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

类索引、父类索引与接口索引集合都顺序排列在访问标志之后。
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件由这三项数据来确定这个类的继承关系。

2.6. 字段

字段表(field_info)用于描述接口或者类中声明的变量。
字段(field)包括类级变量以及实例变量,但不包括方法内部声明的局部变量。

field_info结构格式如下:

field_info {
    u2 access_flags;
    u2 name_index;// 简单名称索引
    u2 descriptor_index;//描述符索引
    u2 attribute_count;// 当前字段附加属性的数量
    attribute_info attributes[attributes_count];
}

attributes项是由多个attribute_info表组成的列表。attributes_count指出列表中attribute_info表的数量。一个字段在其列表中可以有任意数量的属性。由Java虚拟机规范定义的三种可能会出现在此项中的属性是:ConstantValue、Deprecated和Synthetic。Java虚拟机唯一需要识别的属性是ConstantValue属性。虚拟机实现必须忽略任何无法识别的属性。

2.7. 方法

所有的方法(Method),包括实例初始化方法和类初始化方法在内,都由method_info结构所定义。在一个Class文件中,不会有两个方法同时具有相同的方法名和描述符。

method_info结构格式如下:

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

有可能在class文件中出现的两种编译器产生的方法是:实例初始化方法(名为<init>)和类与接口初始化方法(名为<clinit>)。

在此项中可能会出现的由Java虚拟机规范定义的四种属性是:Code、Deprecated、Exceptions和Synthetic。Java虚拟机只需要识别Code和Exception属性。虚拟机实现必须忽略任何无法识别的属性。

2.8. 属性

属性(Attributes)在Class文件格式中的ClassFile结构、field_info结构、method_info结构和Code_attribute结构都有使用,所有属性的通用格式如下:

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];// 属性数据
}

由规范定义的attribute_info表的类型:

名称 使用者 描述
Code method_info 方法的字节码和其他数据
ConstantValue field_info final变量的值
Deprecated field_info、method_info 字段或者方法被禁用的指示符
Exceptions method_info 方法可能抛出的可被检测的异常
InnerClasses ClassFile 内部、外部类的列表
LineNumberTable Code_attribute 方法的行号与字节码的映射
LocalVariableTable Code_attribute 方法的局部变量的描述
SourceFile ClassFile 源文件名
Synthetic field_info、method_info 编译器产生的字段或者方法的指示符

2.8.1. Code属性

Code属性是一个变长属性,位于method_info结构的属性表。定义了方法的字节码序列和其他信息。

一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。所有Java虚拟机实现都必须能够识别Code属性。如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它情况下,method_info必须有明确的Code属性。

Code属性的格式如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;// 当前方法的操作数栈在运行执行的任何时间点的最大深度
    u2 max_locals;// 分配在当前方法引用的局部变量表中的局部变量的个数,包括调用此方法时用于传递参数的局部变量
    u4 code_length;
    u1 code[code_length];//当前方法的Java虚拟机字节码
    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];
}

2.8.2. ConstantValue属性

ConstantValue属性是定长属性,位于field_info结构的属性表中。ConstantValue属性表示一个常量字段的值。在一个field_info结构的属性表中最多只能有一个ConstantValue属性。

ConstantValue属性的格式如下:

ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length; 
    u2 constantvalue_index;
}

2.8.3. Deprecated属性

Deprecated属性是可选定长属性。

Deprecated_attribute {
    u2 attribute_name_index;
    u4 attribute_length;//固定为0
}

2.8.4. Exceptions属性

Exceptions属性是一个变长属性,它位于method_info结构的属性表中。Exceptions属性指出了一个方法需要检查的可能抛出的异常。一个method_info结构中最多只能有一个Exceptions属性。

Exceptions属性格式如下:

Exceptions_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_exceptions;
    u2 exception_index_table[number_of_exceptions];
}

2.8.5. InnerClasses属性

InnerClasses属性是一个变长属性,位于ClassFile结构的属性表。
InnerClasses属性是在JDK1.1中为了支持内部类和内部接口而引入的。

InnerClasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    {
        u2 inner_class_info_index;
        u2 outer_class_info_index;
        u2 inner_name_index;
        u2 inner_class_access_flags;
    }
} classes[number_of_classes];

2.8.6. LineNumberTable属性

LineNumberTable属性是可选变长属性,位于Code结构的属性表。它被调试器用于确定源文件中行号表示的内容在Java虚拟机的code[]数组中对应的部分。
LineNumberTable_attribute属性格式如下:

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

2.8.7. LocalVariableTable属性

LocalVariableTable是可选变长属性,位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息。
LocalVariableTable属性格式如下:

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}

2.8.8. SourceFile属性

SourceFile属性是可选定长字段,位于ClassFile结构的属性表。一个ClassFile结构中的属性表最多只能包含一个SourceFile属性。
SourceFile属性格式如下:

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

2.8.8. Synthetic属性

Synthetic属性是定长属性,位于ClassFile中的属性表。如果一个类成员没有在源文件中出现,则必须标记带有Synthetic属性,或者设置ACC_SYNTHETIC标志。
Synthetic_attribute属性的格式如下:

Synthetic_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
}

参考:
《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》
《深入Java虚拟机》
《Java虚拟机规范(Java SE 7)》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容