声明:本文是学习笔记,主要学习自《深入理解Java虚拟机·JVM高级特性与最佳实践》周志明 著,且本文绝
大部分文字内容直接摘录自此书,部分内容为本人见解,如有不当欢迎指正!
Class文件数据类型:
Class文件是一组以(8位bit的)byte字节为基础单位的二进制流。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。
无符号数:无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节;
无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“info”结尾。表用于描述
有层次关系的复合结构的数据,整个Class文件本质上就是一张表。
Class文件格式示意图:
上图中的u、info均表示数据类型,而不代表数据本身;我们可任意打开一个Class文件:
说明:上图中被绿色框圈起来的则为标准的Class文件的样子(与示意图相比,只是有换行而已)。左侧为软件本身提供
的辅助信息,记录当前行前面总共有多少个byte(或者说多少个u1),用于快速定位数据(通过数据偏移量的方式,
如:当前u2数据的数据偏移量是0x00000012,那么就可定位到当前这两个byte数据分别在字节数组的第19个
和第20个);右侧为直接以编辑器打开class文件的样子;
提示:查看Class文件结构,可使用jdk自带的javap指令,也可以使用WinHex等工具,本人使用的是classpy查看的。
Class文件所含内容汇总(按格式):
名称 | 数据类型(无符号数/表) | 数量 |
magic(魔数) | u4 | 1 |
minor_version | u2 | 1 |
major_version | u2 | 1 |
constant_pool_count | u2 | 1 |
constant_pool常量池 | cp_info集合 | constant_pool_count - 1 |
access_flag | u2 | 1 |
this_class | u2 | 1 |
super_class | u2 | 1 |
interfaces_count | u2 | 1 |
interfaces | u2 | interfaces_count |
fields_count | u2 | 1 |
fields | field_info集合 | fields_count |
methods_count | u2 | 1 |
methods | method_info集合 | methods_count |
attributes_count | u2 | 1 |
attributes | attribute_info集合 | attributes_count |
各部分内容说明:
①魔数
每个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件。所有Class文件,魔数均为0xCAFEBABE。
注:不仅仅是Class文件,还有很多文件(如:gif、jpeg等)都是以魔数(而不以后缀名)来进行身份识别的。
②次版本号、主版本号
说明:Class文件中第五、六个字节存储的是次版本号(minor version),第七、八个字节存储的是
主版本号(major version)。假设Class文件的版本号十进制下为45.3,那么major version在
十进制下为45,minor versio在十进制下则为3。
注:Class文件能够被版本号对应jdk版本(或比对应版本高)的jdk加载,不能被比对应jdk版本低的jdk加载。jdk中的版本
号是从45开始的,每个jdk的target参数(如果有的话)的参数值对应一个主版本号:
jdk的target参数值 | 十进制Class版本号 |
-target 1.1 | 45 |
-target 1.2 | 46 |
-target 1.3 | 47 |
-target 1.4 | 48 |
-target 1.5 | 49 |
-target 1.6 | 50 |
-target 1.7 | 51 |
-target 1.8 | 52 |
…… |
以下为jdk版本与Class版本号详细对应信息(部分):
Jdk版本 | -target参数 | 十六进制Class版本号 | 十进制Class版本号 |
Jdk 1.1.8 | 不能带-target参数 | 00 03 00 2D | 45.3 |
Jdk 1.2.2 | 不带(默认为-target 1.1) | 00 03 00 2D | 45.3 |
Jdk 1.2.2 | -target 1.2 | 00 00 00 2E | 46.0 |
Jdk 1.3.1_19 | 不带(默认为-target 1.1) | 00 03 00 2D | 45.3 |
Jdk 1.3.1_19 | -target 1.3 | 00 00 00 2D | 47.0 |
Jdk 1.4.2_10 | 不带(默认为-target 1.2) | 00 00 00 2F | 46.0 |
Jdk 1.4.2_10 | -target 1.4 | 00 00 00 30 | 48.0 |
Jdk 1.5.0_11 | 不带(默认为-target 1.5) | 00 00 00 31 | 49.0 |
Jdk 1.5.0_11 | -target 1.4 -source 1.4 | 00 00 00 30 | 48.0 |
Jdk 1.6.0_01 | 不带(默认为-target 1.6) | 00 00 00 32 | 50.0 |
Jdk 1.6.0_01 | -target 1.5 | 00 00 00 31 | 49.0 |
Jdk 1.7.0 | 不带(默认为-target 1.7) | 00 00 00 33 | 51.0 |
Jdk 1.7.0 | -target 1.6 | 00 00 00 32 | 50.0 |
Jdk 1.7.0 | -target 1.4 -source 1.4 | 00 00 00 30 | 48.0 |
Jdk 1.8.0 | 不带(默认为-target 1.8) | 00 00 00 34 | 52.0 |
…… |
③常量池计数器、常量池:
常量池可以理解为Class文件中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最多的数据项目之一,同时它还是Class文件中第一个出现的表类型数据项目。
常量池计数器:
由于常量池中的常常数的数量是不固定的,所以在常量池的入口放置了一个u2类型的数据,来代表常量池容器记数值(constant_pool_count)。
注:由于constant_pool_count数据类型为u2(即:两个byte):所以constant_pool_count不能超过65535。
常量池计数器默认从1开始而不是从0开始:
常量池计数器默认从1开始而不是从0开始,即:当constant_pool_count = 1时,常量池中的cp_info个数为0;当constant_pool_count为n时,常量池中的cp_info个数为n-1。
原因:
在指定class文件规范的时候,将索引#0项常量空出来是有特殊考虑的,这样当:某些数据在特定的情况下想表达“不引用任何一个常量池项”的意思时,就可以将其引用的常量的索引值设置为#0来表示。可以参考下面这个概念性的图来辅助理解:
常量池:
常量池紧随着常量池计数器(打开的class文件较复杂,常量池数据太长,以下仅为部分截图):
常量池中主要存放着两种常量,字面量(Literal)和符号引用(Synbolic References)。
字面量包括:文本字符、声明为final的常量值、基础数据类型的值等。
符号引用包括:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
注:全限定类名中的“.”,会被“/”替代,存储进常量池中。如:“com.aspire.water.MyClass”存进存进常量池
中为”com/aspire/water/MyClass”。
注:常量池中存储着最基本的信息,不仅程序中(将常量池加载为运行时常量池)会用到该信息,Class文件本身的
一些地方,也会通过#索引的方式用到常量池中的值。
符号引用与直接引用:
符号引用在javac编译时产生,完全符合Java虚拟机规范;编译产生class文件,而class文件此时还没有被JVM加载,此时的符号引用仅仅是一个标识而已,符号引用并不知道其对应的数据放在哪一处内存;当JVM加载class文件时,会将具体的信息提取出来放入内存(不同的JVM对内存的布局机制不一定相同),进而就知道了哪一条信息处于哪一处内存,由于符号引用完全符合java虚拟机规范,所以这时根据每一处信息的内容,逆推出该信息对应的符号引用,然后再使用该信息的实际的内存地址来替代符号引用。这种替换操作发生在类加载过程(加载 -> 连接(验证、准备、解析) -> 初始化)中的解析阶段,会将符号引用转换(替换)为对应的直接引用,放入运行时常量池中。
注:直接引用可以是指向目标内存的指针,也可以是偏移量,也可以是一个能定位到目标内存的句柄。
cp_info的大体结构为:
注:cp_info又可细分为14种结构类型,其中tag为各个常量池项类型的类型标识符。
cp_info又可细分为14种类型:
类型 | 标志(tag) | 描述 |
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 | 表示一个动态方法调用点 |
他们的结构分别为:
CONSTANT_Utf8_info的结构为:
CONSTANT_Integer_info结构为:
CONSTANT_Float_info结构为:
CONSTANT_Long_info结构为:
CONSTANT_Double_info结构为:
CONSTANT_Class_info结构为:
CONSTANT_String_info结构为:
CONSTANT_Fieldref_info结构为:
CONSTANT_Methodref_info结构为:
CONSTANT_InterfaceMethodref_info结构为:
CONSTANT_NameAndType_info结构为:
CONSTANT_MethodHandle_info结构为:
CONSTANT_MethodType_info结构为:
CONSTANT_InvokeDynamic_info结构为:
这十四种类型的结构汇总:
常量 | 项目 | 类型 | 描述 |
CONSTANT_Utf8_info | tag | u1 | 值为1 |
length | u2 | UTF-8编码的字符串占用的字节数 | |
bytes | u1集合 | 长度为length的UTF-8编码的字符串 | |
CONSTANT_Integer_info | tag | u1 | 值为3 |
bytes | u4 | 按照高位在前存储的int值 | |
CONSTANT_Float_info | tag | u1 | 值为4 |
bytes | u4 | 按照高位在前存储的float值 | |
CONSTANT_Long_info | tag | u1 | 值为5 |
bytes | u8 | 按照高位在前存储的long值 | |
CONSTANT_Double_info | tag | u1 | 值为6 |
bytes | u8 | 按照高位在前存储的double值 | |
CONSTANT_Class_info | tag | u1 | 值为7 |
index | u2 | 指向全限定名常量项的索引 | |
CONSTANT_String_info | tag | u1 | 值为8 |
index | u2 | 指向字符串字面量的索引 | |
CONSTANT_Fieldref_info | tag | u1 | 值为9 |
index | u2 | 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向字段描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_Methodref_info | tag | u1 | 值为10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_InterfaceMethodref_info | tag | u1 | 值为11 |
index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_NameAndType_info | tag | u1 | 值为12 |
index | u2 | 指向该字段或方法名称常量项的索引 | |
index | u2 | 指向该字段或方法描述符的索引 | |
CONSTANT_MethodHandle_info | tag | u1 | 值为15 |
reference_kind | u2 | 值必须在[1,9]之间,它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为 | |
reference_index | u2 | 值必须是对常量池的有效引用 | |
CONSTANT_MethodType_info | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效引用,常量池在索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符 | |
CONSTANT_InvokeDynamic_info | tag | u1 | 值为18 |
bootstrap_method_attr_index | u2 | 值必须是对当前Class文件中引导方法表的bootstrap methods[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该处的索引必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
④访问标志(access_flags)
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags)。
访问标志用于识别一些类或接口层次的访问信息:
标志名称 | 标志值 | 含义 |
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 | 标识这是一个枚举 |
注:多个标识符的话,access_flags的值累加即可;由于标识符对应的值的特殊性,类加后的结果,可唯一定位有哪
些标识符,如:0x0021只有ACC_SUPER与ACC_PUBLIC的值累加后才得得到;0x0031只有ACC_SUPER与
ACC_FINAL与ACC_PUBLIC的值累加后才得得到。
注:access_flags中一共有16个标志位可以使用,当前只定义了其中八个。
⑤类索引、父类索引、接口索引计数器、接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,接口索引计数器也是一个u2类型的数据,接口索引集合则是一组u2类型数据的集合。Class文件中的这几项数据来确定这个类的继承关系。
类索引:确定当前类的全限定名。
父类索引:确定当前类的父类的全限定名。
注:由于Java单继承的原则,父类只可能有一个;由于Object是所有其他类的基类,所以除了Object类本身
的super_class为0外;其余所有类的super_class都不为0。
接口索引(接口索引计数器、接口索引集合):按照当前类implements(或当前接口extends)的接口的顺序,从左
到右排列在接口索引集合中。
注:顾名思义,类索引的直接值、父类索引的直接值、接口索引集合中的索引的直接值,代表的是一个常量池中的
索引值;该索引值对应为一个CONSTANT_Class_info,CONSTANT_Class_info又指向CONSTANT_Utf8_info,
CONSTANT_Utf8_info中存储着全限定名字符串信息。
如,当前super_class为:
类索引查找全限定名的过程为:
⑥字段表集合(当前类字段计数器、字段表集合)
先用classpy观察一个样例:
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量(即;静态变量)以及实例变量(即:非静态变量),但不包括在方法内部声明的局部变量。
field_info结构为:
field_info结构(以表格的形式展示):
名称 | 类型 | 数量 |
access_flags | u2 | 1 |
name_index | u2 | 1 |
descriptor_index | u2 | 1 |
attributes_count | u2 | 1 |
attributes | attribute_info | attributes_count |
field的access_flags:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否enum |
注:类有类的access_flags,字段有字段的access_flags。
field的name_index:
field的自定义名称(即;字段名),如:
的name_index为message。
field的descriptor_index:
field的类型描述(字段类型描述)。
其中,标识字符有:
标识字符 | 含义 |
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L + 类全限定名 + “;”号 | 对象类型,如: Ljava/lang/String; |
注:数组类型的描述,每一维度,需要在前面加一个“[”;如:
二维数组java.lang.String[][],将被记录为“[[Ljava/lang/String;”,int[]将被记录为“[I”。
示例:
的descriptor_index为Ljavax.mail.internet.MimeMessage;。
⑦方法表集合(方法计数器、方法表)
先用classpy观察一个样例:
method_info结构为:
注:method_info结构与field_info是一模一样的。
method_info结构(以表格的形式展示):
名称 | 类型 | 数量 |
access_flags | u2 | 1 |
name_index | u2 | 1 |
descriptor_index | u2 | 1 |
attributes_count | u2 | 1 |
attributes | attribute_info | attributes_count |
method的access_flags:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 方法是否为public |
ACC_PRIVATE | 0x0002 | 方法是否为private |
ACC_PROTECTED | 0x0004 | 方法是否为protected |
ACC_STATIC | 0x0008 | 方法是否为static |
ACC_FINAL | 0x0010 | 方法是否为final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否为synchronized |
ACC_BRIDGE | 0x0040 | 方法是否是由编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 字段是否接受不定参数 |
ACC_NATIVE | 0x0100 | 字段是否为native |
ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
ACC_STRICTFP | 0x0800 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否是由编译器自动产生的 |
注:类有类的access_flags,字段有字段的access_flags,方法有方法的access_flags。
method的name_index:
如:
的name_index为initSmtpParams。
method的descriptor_index:此描述符,可描述方法的参数类型、返回值类型。
descriptor_index = 方法各个形参(严格按照从左到右的顺序)的标识字符 + 方法返回值类型的标识字符。
其中,标识字符有:
标识字符 | 含义 |
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L + 类全限定名 + “;”号 | 对象类型,如: Ljava/lang/String; |
注:数组类型的描述,每一维度,需要在前面加一个“[”;
如:二维数组java.lang.String[][],将被记录为“[[Ljava/lang/String;”,int[]将被记录为“[I”。
如:
的descriptor_index为()V;
的descriptor_index为(I[Ljava/lang/String;[[I)Z。
method的attributes:方法声明的异常信息、方法的实际逻辑代码,放在attributes里面。
⑧属性表attribute_info集合
声明:我们常说的什么静态属性、非静态属性,其实指的是field字段。严格来讲属性指的应该是attributes。
属性表出现的位置(先用classpy看一个示例):
Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。
注:attributes本身也可以嵌套attributes。
与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不能识别的属性。《Java虚拟机规范(Java SE 7)》中预定义了21项虚拟机实现应当能识别的属性。
我们先看一下attribute_info的总体结构:
给出表格版:
名称 | 类型 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
attribute_info又可细分为以下21种(即《Java虚拟机规范(Java SE 7)》中预定义了的21项虚拟机实现应当能识别的属性):
属性名称 | 使用位置 | 含义 |
Code | 方法表中 | Java代码编译成的字节码指令(即:具体的方法逻辑字节码指令) |
ConstantValue | 字段表中 | final关键字定义的常量值 |
Deprecated | 类中、方法表中、字段表中 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表中 | 方法声明的异常 |
LocalVariableTable | Code属性中 | 方法的局部变量描述 |
LocalVariableTypeTable | 类中 | JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
InnerClasses | 类中 | 内部类列表 |
EnclosingMethod | 类中 | 仅当一个类为局部类或者匿名类时,才能拥有这个属性,这个属性用于表示这个类所在的外围方法 |
LineNumberTable | Code属性中 | Java源码的行号与字节码指令的对应关系 |
StackMapTable | Code属性中 | JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类中、方法表中、字段表中 | JDK1.5新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息 |
SourceFile | 类中 | 记录源文件名称 |
SourceDebugExtension | 类中 | JDK1.6中新增的属性,SourceDebugExtension用于存储额外的调试信息。如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码运行在Java虚拟机汇中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension就可以存储这些调试信息。 |
Synthetic | 类中、方法表中、字段表中 | 标识方法或字段为编译器自动产生的 |
RuntimeVisibleAnnotations | 类中、方法表中、字段表中 | JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性,用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。 |
RuntimeInvisibleAnnotations | 类中、方法表中、字段表中 | JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations相反用于指明哪些注解是运行时不可见的。 |
RuntimeVisibleParameterAnnotations | 方法表中 | JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations类似,只不过作用对象为方法的参数。 |
RuntimeInvisibleParameterAnnotations | 方法表中 | JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations类似,只不过作用对象为方法的参数。 |
AnnotationDefault | 方法表中 | JDK1.5中新增的属性,用于记录注解类元素的默认值 |
BootstrapMethods | 类中 | JDK1.7新增的属性,用于保存invokedynamic指令引用的引导方法限定符 |
对部分常见的attribute_info进行简单说明:
Code属性:
Java程序方法体汇总的代码经过javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有分反方都必须存在这个属性,如:接口或者抽象类中的抽象方法就不存在Code属性,如果Code属性存在,那么他的结构是这样的:
以表格的形式展示:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
max_stack | u2 | 1 |
max_locals | u2 | 1 |
code_length | u4 | 1 |
code | u1 | code_length |
exception_table_length | u2 | 1 |
exception_table | exception_info | exception_table_length |
attribute_count | u2 | 1 |
attributes | attribute_info | attribute_count |
其中exception_table的结构为:
名称 | 类型 | 数量 |
start_pc | u2 | 1 |
end_pc | u2 | 1 |
handler_pc | u2 | 1 |
catch_type | u2 | 1 |
attribute_name_index:一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,它代表了属性的
名称。
attribute_length:指示了属性值的长度。由于attribute_name_index与attribute_length一共占6个字节,所以属性
值的长度又等于属性表总长度-6。
max_stack:操作数栈(Operand Stacks)允许深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。
虚拟机的时候需要根据这个值来分配栈帧(Stack Frame)中的操作占深度。
max_locals:代表了局部变量表所需的存储空间。在这里,max_locals的单位是Slot槽。Slot是虚拟机为局部
变量分配内存所使用的最小单位。一个Slot的空间大小为四字节,对于byte、char、float、int、
short、 boolean、和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个Slot;
而double和long这种64位的数据则需要两个连续的Slot来存储。
注:returnAddress为方法的返回结果,可能是一个具体的值,也可能是一个指向堆中的地址。
注:方法参数(包括实例方法的隐藏参数this)、显示异常处理参数(即:try catch使,catch处的参数)、方法体中的局部
变量都需要局部变量表来存放。
追注:实例方法有隐藏的参数this,而静态方法没有。
注:并不是在方法中用到了多少局部变量,就把这些局部变量所占用放入Slot个数之和作为,max_locals的值,原因是局
部变量表中的Slot槽可以重用。当代码执行超出一个局部变量的做用户与使时,这个局部变量所占用放入Slot可以被
其它局部变量所使用,Javac编译器会根据变量的作用域来分配Slot给各个变量使用,然后计算出max_locals的大
小。
code_length、code:用来存储Java源码编译后的字节码指令。code_length代表字节码长度;code是用于存储
Java字节码指令的一系列字节流。
注:每个指令就是一个u1类型的单字节。关于指令的含义可自行查阅“虚拟机字节码指令表”。
注:Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法体中的具体逻
辑代码)和元数据(Metadata,包括类、字段、方法定义以及其它信息)两部分的话,那么在整个Class文件中,
Code属性用于描述代码,所有其它的数据项目都用于描述元数据。
exception_table_length、exception_table:首先异常(处理)表对于Code来说并不是必须的。执行字节码时,
如果字节码在[start_pc“行”, end_pc“行”)之间出现了类型为
catch_type或者其子类的异常(catch_type为指向一个
CONSTANT_Class_info型常量的索引),则转到第
handler_pc“行”继续处理。当catch_type的值为0时,代表任意
异常情况都需要转向到handler_pc“行”处进行处理。
注:这里的“行”是指字节码相对于方法开始的偏移量,而不是Java源代码中的行。
注:异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转指令来实现Java异常及finally处理机制。
Exceptions属性:
Exception属性的作用是列举出方法的声明异常。
其结构为:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
number_of_exceptions | u2 | 1 |
exception_index_table | u2 | number_of_exceptions |
注:exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了异常的类型。
LineNumberTable属性:
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。
注:LineNumberTable并不是必须的,javac编译时,可通过-g:none或-g:lines来取消或生成这项信息。
LineNumberTable属性的结构:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
line_number_table_length | u2 | 1 |
line_number_table | line_number_info | line_number_table_length |
LocalVariableTable属性:
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间关系。
注:LocalVariableTable属性不是必须的,在javac编译时,可通过-g:none或-h:vars来取消或关闭这项信息。
注:如果没有生成这项信息,最大的影响就是当别人引用这个方法时,所有的参数名称都将失去,IDE将会使
用诸如arg0、arg1之类的占位符来代替原有的参数名,这对程序没什么影响,但是会对代码编写带来较大
不便,而且在调试期间无法根据参数名称从上下文中获取参数值。
LocalVariableTable属性结构为:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
local_variable_table_length | u2 | 1 |
local_variable_table | local_variable_info | local_variable_table_length |
其中,local_variable_info代表了一个栈帧与源码中的局部变量的关联,其机构为:
名称 | 类型 | 数量 |
start_pc | u2 | 1 |
length | u2 | 1 |
name_indec | u2 | 1 |
descriptor_index | u2 | 1 |
index | u2 | 1 |
注:start_pc和length属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围的覆盖长度。
即:确定了这个局部变量的作用范围。
注:nam_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变
量的名称以及这个局部变量的描述符。
注:index是这个局部变量在栈帧局部变量表中Slot的位置。如果这个局部变量是64位的,那么它占用的两个连
续的Slot的位置是index和index+1。
注:JDK1.5之后,新增了一个LocalVariableTable属性的“姐妹属性”---LocalVariablrTypeTablem,这个新增的属
性结构与LocalVariableTable非常相似,仅仅是把记录字段描述符的descripor_index替换成了字段的特征签
名(Signature),对于非泛型类型来说,描述符和特征签名描述的信息基本是一致的,但是引入泛型后,由于
描述符中泛型的参数类型被擦除掉,描述符就不能准确地描述泛型类型了,因此出现录入
LocalVariableTypeTable。
SourceFile属性:
SourceFile属性用于记录生成这个Class文件的源码文件名称。
注:此属性是可选的,javac编译时,可通过设置-g:none或-g:source来选择关闭或生成这项信息。
SourceFile属性结构为:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
sourcefile_index | u2 | 1 |
注:sourcefile_index是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名。
ConstantValue属性:
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(即:类变量)才可以使用这项属性。对于类中的实例变量(即:非静态变量),赋值操作是在实例构造器<init>中进行的。对于类变量(即:静态变量),有两种赋值方式可以选择:一种是在类构造器<clinit>方法中进行赋值;另一种是使用ConstantValue属性进行赋值。
注:目前Sun Javac编译器的选择是:如果这个变量的数据类型是基本类型或者java.lang.String的话,就生成
ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本数据类型及字符串,则将
会选择在<clinit>方法(即:类构造器)中进行初始化。
ConstantValue属性结构:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
constantvalue_index | u2 | 1 |
注:constantvalue_index数据项代表了常量池中一个字面量常量的引用,根据字段类型的不同,这里的字面
量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、
CONSTANT_Integer_info、CONSTANT_String_info常量中的一种。
InnerClass属性:
InnerClass属性用于记录内部类与宿主类之间的关联。
InnerClass属性的结构:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
number_of_class | u2 | 1 |
inner_classes | inner_classes_info | number_of_class |
注:number_of_classes代表记录了多少个内部类信息,每一个内部类信息都由一个inner_classes_info表进行描述。
inner_classes_info结构为:
名称 | 类型 | 数量 |
inner_class_info_index | u2 | 1 |
outer_class_info_index | u2 | 1 |
inner_name_index | u2 | 1 |
inner_class_access_flags | u2 | 1 |
注:inner_class_info_index和outer_class_info_index都是指向常量池CONSTANT_Class_info型常量的索引,分别代
表了内部类和宿主类的符号引用。
注:inner_name_index是指向常量池CONSTANT_Utf8_info型常量的索引,代表这个内部类的名称,如果是匿名内部
类,那么这项值为0。
注:inner_class_access_flags是内部类的访问标志,类似于类的access_flags。
示例:
Deprecated及Synthetic属性:
Deprecated和Synthetic这两个属性属于标志类型的属性。
Deprecated属性用于表示某个类、字段或者方法,已经被程序作者不再推荐使用,它可以通过在代码中使用@deprecated注释来进行设置。
Synthetic属性用于表示此字段或方法并不是由Java源码直接产生,而是由编译器自动添加的,在JDK1.5之后,标识一个类、字段或者方法是编译器自动产生的,也可以设置它们的访问标志中的ACC_SYNTHETIC标志位,其中最经典的例子就是Bridge Method。所有由非用户代码产生的类、方法及字段都应当至少设置Synthtic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外就是实例构造器<init>和类构造器<clinit>。
Deprecated及Synthetic的结构均为:
给出表格版:
名称 | 类型 | 数量 |
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
声明:本文是学习笔记,主要学习自以下书籍及视频