文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 概述
- Java 实现跨平台靠的是虚拟机技术,将源文件编译成与操作系统无关的,只有虚拟机能识别并执行的字节码文件,由各个操作系统上的 Java 虚拟机来负责执行,屏蔽了底层具体的操作系统,通过这种方式实现了 平台的无关性。
- 仍然是通过虚拟机技术,同时还实现了 语言的无关性,Java 虚拟机不和包括 Java 语言在内的任何语言绑定,它只与 Class 文件这种特定的二进制文件格式所关联,Class 文件中包含了 Java 虚拟机指令集和符号表以及若干其他辅助信息。
- Java 虚拟机规范要求在 Class 文件中使用许多强制性的语法和结构化约束,但任何一门功能性语言都可以表示为一个被 Java 虚拟机所接受的有效的 Class 文件。
2. Class 类文件结构
- Class 文件是一组 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加任何分隔符,这使得整个 Class 文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。
- 当遇到需要占用 8 位字节以上空间的数据项时,则会按照 高位在前 的方式分割成若干个 8 位字节进行存储。
- 根据 Java 虚拟机规范的规定,Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储的,这种伪结构中只有两种数据类型:无符号数 和 表。
- 无符号数属于 基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值,或者按照UTF-8编码构成字符串值。
- 表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表都习惯性地以 " _info " 结尾。
- 表用于描述有层次关系的复核结构的数据,整个 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_flags |
访问标志 |
u2 |
1 |
this_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_cound |
属性个数 |
u2 |
1 |
attributes |
属性 |
attribute_info |
attributes_cound |
- 其中常量、接口、字段、方法和属性在其中按各自的结构紧密排列,个数由其前面的数量字段决定。同时类文件中最小单位为 1 个字节(8 位),超过一个字节的数据以 Big-Endian 方式 存储(指最高位字节在地址最低位、最低位字节在地址最高位的顺序来存储数据,是 SPARC、PowerPC 等处理器的默认多字节存储顺序,而 x86 等处理器则是使用了相反的 Little-Endian 顺序来存储数据)。
- Class 文件中数据项无论是顺序还是数量,甚至于数据存储的字节序这样的细节,都被严格限定,不允许改变。
执行样例
public class Test {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[1 * _1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}
编译后的 Class 内容
1.1 魔数(Magic Number)
- 每个 Class 文件的 头 4 个字节 称为魔数,它的唯一作用就是 确定这个文件是否为一个能被虚拟机接受的 Class 文件。
- 很多文件存储标准都是使用魔数而不是扩展名(扩展名可以被修改)来进行识别,主要是基于安全方面考虑。
- 魔数占文件的开始 4 个字节,为 0xCAFEBABE。
1.2 版本号
-
第 5 和第 6 个字节 是 次版本号(Minor Version),第 7 和第 8 个字节 是 主版本号(Major Version)。
- Java 版本号从 45 开始,每个大版本发布 主版本号 +1。
- 高版本的 JDK 可以向下兼容之前版本的 Class 文件,但不能运行之后版本的 Class 文件。
版本号
- Class 文件中魔数(4 个字节 CAFE BABE)、次版本号(0000)、 主版本号(0034),十六进制 34(10 进制 52)表示 1.8 版本。
1.3 常量池
- 常量池是 Class 文件之中的资源仓库,是 Class 文件结构中与其他项目关联最多的数据类型,也是占用 Class 文件空间最大的数据项目之一,同时还是在 Class 文件中第一个出现的表类型数据项目。
- 主要存放两大类常量,字面量 和 符号引用。
- 由于常量池中常量的数量不是固定的,所以在常量池的入口需要放置一项 u2 类型(两个字节)的数据 u,代表 常量池容量计数值,这个容量计数值是从 1 开始的(这样做的目的在于满足某些指向常量池的索引值的数据在特定情况下需要表达 " 不引用任何一个常量池项目 " 的含义,这种情况就可以把索引值置为 0 来表示)。
- 字面量(Literal):类似 Java 中的常量,如文本字符串,声明为 final 的常量值等。
- 符号引用(Symbolic References):包括类和接口的全限定名(Full Qualified Name),字段的名称和描述符(Descriptor),方法的名称和描述符这三类常量。
- Java 代码在编译时没有连接的步骤,而是在虚拟机加载 Class 文件的时候进行动态连接。
- 当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
- 常量池中每一项常量都是一个表,到 JDK 1.7 为止,共有 14 种常量表类型。
- 所有常量表开始第一位为 u1 类型的标志位,标识 常量类型。
常量池的项目类型 |
标志位 |
描述 |
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 |
表示一个动态方法调用点。 |
- 每一种常量类型有着自己的结构,其结构如下表。
- 读取常量池的时候首先读取标志位,判断常量类型,就可以知道对应的结构,获取对应的信息了。
- 由于 Class 文件中方法、字段等都需要引用 CONSTANT_UTF8_info 型常量来描述名称,所以 CONSTANT_UTF8_info 型常量的最大长度也就是 Java 中方法和字段名的最大长度。
- 最大值长度是 65535,所以 Java 程序中如果定义了超过 64kb 英文字符的变量或方法名,将会无法编译。
常量 |
项目 |
类型 |
描述 |
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_Interface_Methodref_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 |
u1 |
值必须在 [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 结构,表示方法名和方法描述符。 |
常量池
- 除去前 8 个字节(魔数+版本号),紧跟的 0x001E(十进制 30)表明常量池包含有 30 项常量(索引 1-29)。
- 0x0A(十进制 10)表明常量类型是 " 类中方法的符号引用 ",该常量结构为 0x0A0005001B。
- 0x0005(十进制 5)表明 " 指向声明方法的类描述符 CONSTANT_Class_info 的索引 " 为 5。
- 0x001B(十进制 27)表明 " 指向名称及类型描述符 CONSTANT_NameAndType 的索引 " 为 27。
通过 javap 查看字节码
Constant pool:
#1 = Methodref #5.#27 // java/lang/Object."<init>":()V
#2 = Class #28 // services/Test
#3 = Integer 262144
#4 = Integer 4194304
#5 = Class #29 // java/lang/Object
#6 = Utf8 _1MB
#7 = Utf8 I
#8 = Utf8 ConstantValue
#9 = Integer 1048576
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lservices/Test;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 allocation1
#22 = Utf8 [B
#23 = Utf8 allocation2
#24 = Utf8 allocation3
#25 = Utf8 SourceFile
#26 = Utf8 Test.java
#27 = NameAndType #10:#11 // "<init>":()V
#28 = Utf8 services/Test
#29 = Utf8 java/lang/Object
1.4 访问标志
- 常量池之后的两个字节代表访问标志,即这个 Class 是类还是接口,是否为 public 等信息。不同的含义有不同的标志值。
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否为 public 类型。 |
ACC_FINAL |
0x0010 |
是否被声明为 final,只有类可设置。 |
ACC_SUPER |
0x0020 |
是否允许使用 invokespecial 字节码指令的新语言,invokespecial 指令的语意在 JDK 1.0.2 发生过改变,为了区别这条指令使用哪种语意,JDK 1.0.2 之后编译出来的类的这个标志都必须为真。 |
ACC_INTERFACE |
0x0200 |
标识这是一个接口。 |
ACC_ABSTRACT |
0x0400 |
是否为 abstract 类型,对于接口或者抽象类来说,此标志值为真,其他类值为假。 |
ACC_SYNTHETIC |
0x1000 |
标识这个类并非由用户代码产生的。 |
ACC_ANNOTATION |
0x2000 |
标识这是一个注解。 |
ACC_ENUM |
0x4000 |
标识这是一个枚举。 |
1.5 类索引
- 类索引占 两个字节(u2),指向常量池中的 CONSTANT_Class_info 类型的常量,这个类型的常量结构包含一个指向 全限定名常量项的索引(CONSTANT_Utf8_info)。
1.6 父类索引
- 因为 Java 只允许单继承,所以只有一个父类,具体内容与类索引一致。
- 除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。
1.7 接口索引
- 接口索引开始两个字节用来表示接口的数量,之后的每两个字节表示一个接口索引,用法同类索引与父类索引。
1.8 字段表集合
- 字段用于描述接口或者类中声明的变量,包括类级变量以及实例变量,但 不包括局部变量。
- 字段表使用标志位表示修饰符,引用常量池中的常量描述字段名及字段数据类型。
- 字段表集合中不会列出从超类或者父接口中继承而来的字段,但可能列出原本 Java 代码之中不存在的字段,例如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
- Java 语言中字段是无法重载的,必须使用不同的名称,但是对于字节码来说,字段可以重名,只要字段的描述符不一致。
- 字段域的开始两个字节表示字段数量,之后为紧密排列的字段结构体数据,其结构如下。
名称 |
类型 |
数量 |
描述 |
access_flags |
u2 |
1 |
访问标志。 |
name_index |
u2 |
1 |
字段的简单名称。 |
descriptor_index |
u2 |
1 |
字段和方法的描述符。 |
attributes_count |
u2 |
1 |
属性表数量。 |
attributes |
attribute_info |
attributes_count |
属性表。 |
- access_flags 表示字段修饰符,与类的 access_flags 类似,并且都是一个 u2 的数据类型。
- name_index 和 descriptor_index 都是对常量池的引用。
- name_index 代表字段的简单名称。
- descriptor_index 代表字段和方法的描述符。
标志名称 |
标志值 |
含义 |
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。 |
全限定名
- 将类全名中的 " . " 替换成 " / ",并在最后添加一个 " ; ",表示全限定名结束。
简单名称
描述符
标志字符 |
含义 |
B |
byte |
C |
char |
D |
double |
F |
float |
I |
int |
J |
long |
S |
short |
Z |
boolean |
V |
void |
L |
对象类型 |
[ |
数组 |
1.9 方法表集合
- Class 文件中对方法的描述与以前对字段的描述几乎采用了完全一致的方式,唯一的区别就是访问类型不完全一致。
标志名称 |
标志值 |
含义 |
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 |
方法是否是由编译器自动产生的。 |
- 如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息,但有可能出现由编译器自动添加的方法,如 类构造器 <clinit> 方法 和 实例构造器 <init> 方法。
1.10 属性表集合
- Class 文件、字段表、方法表都可以有自己的属性表集合,用于描述某些场景的专有信息。属性表集合的限制更宽松一些,不要求各个属性表具有严格顺序,并且只要不与已有属性名重复即可。
- 可以自定义属性信息,Java 虚拟机运行时会忽略不认识的属性。
- 符合规范的属性表基本结构如下。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
sourcefile_index |
u2 |
1 |
- 其中前两个字节为指向常量池中的 CONSTANT_Utf8_info 类型的属性名称,之后 4 个字节表示属性值所占用的位数,最后才是具体属性。
属性名称 |
使用位置 |
含义 |
Code |
方法表 |
Java 代码编译成的字节码指令。 |
ConstantValue |
字段表 |
final 关键字定义的常量值。 |
Deprecated |
类、方法表、字段表 |
被声明为 deprecated 的方法和字段。 |
Exceptions |
方法表 |
方法抛出的异常。 |
EnclosingMethod |
类文件 |
仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法。 |
InnerClasses |
类文件 |
内部类列表。 |
LineNumberTable |
Code 属性 |
Java 源码的行号与字节码指令的对应关系。 |
LocalVariableTable |
Code 属性 |
方法的局部变量描述。 |
StackMapTable |
Code 属性 |
JDK 1.6 中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配。 |
Signature |
类、方法表、字段表 |
JDK 1.5 中新增的属性,这个属性用于支持泛型情况下的方法签名,在 Java 语言中,任何类、接口,初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Type),则 Signature 属性会为它记录泛型签名信息。由于 Java 的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息。 |
SourceFile |
类文件 |
记录源文件名称。 |
SourceDebugExtension |
类文件 |
JDK 1.6 中新增的属性,SourceDebugExtension 属性用于存储额外的调试信息。譬如在进行 JSP 文件调试时,无法通过 Java 堆栈来定位到 JSP 文件的行号,JSR-45 规范为这些非 Java 语言编写,却需要编译成字节码并运行在 Java 虚拟机中的程序提供了一个进行调试的标准机制,使用 SourceDebugExtension 属性就可以用于存储这个标准所新加入的调试信息。 |
Synthetic |
类、方法表、字段表 |
标识方法或字段为编译器自动生成的。 |
LocalvariableTypeTable |
类 |
JDK 1.5 中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加。 |
RuntimeVisibleAnnotations |
类、方法表、字段表 |
JDK 1.5 中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations 属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。 |
RuntimeInvisibleAnnotations |
类、方法表、字段表 |
JDK 1.5 中新增的属性,作用与 RuntimeVisibleAnnotations 属性作用刚好相反,用于指明那些注解是运行时不可见的。 |
RuntimeVisibeParameterAnnotations |
方法表 |
JDK 1.5 中新增的属性,作用与 RuntimeVisibleAnnotations 属性类似,只不过作用对象为方法参数。 |
RuntimeInvisibeParameterAnnotations |
方法表 |
JDK 1.5 中新增的属性,作用与 RuntimeInvisibleAnnotations 属性类似,只不过作用对象为方法参数。 |
AnnotationDefault |
方法表 |
JDK 1.5 中新增的属性,用于记录注解类元素的默认值。 |
1.10.1 Code 属性
- 用来存储 Java 程序方法体中的代码经过编译处理后生成的字节码指令。
- 每个指令是一个 u1 类型的单字节,共可以表达 256 条指令。
名称 |
类型 |
数量 |
含义 |
attribute_name_index |
u2 |
1 |
指向常量池中一个 CONSTANT_Utf8_info 类型的常量,表示属性名称。 |
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 |
异常属性表。 |
attributes_count |
u2 |
1 |
Code 属性总数。 |
attributes |
attribute_info |
attributes_count |
Code 属性。 |
- code_length 类型为 u4,理论上最大可以达到 2^32-1,但虚拟机规定一个方法不能超过 65535 条字节码指令,否则 Javac 编译器会拒绝编译。
- Slot 是虚拟机为 局部变量分配内存所只用的最小单位,Javac 编译器会根据变量的作用域来分配 Slot 给各个变量使用。
- Code 属性是 Class 文件中最重要的一个属性,如果一个 Java 程序中的信息分为代码(Code,方法体里的 Java 代码)和元数据(Metadata,包括类、字段、方法定义及其它信息)两部分,那么在整个 Class 文件里,Code 属性用于描述代码,其它的所有数据项目就都用于描述元数据。
异常属性表(exception_table)
- 在字节码指令之后的是这个方法的显示异常处理表,异常表对于 Code 属性表来说不是必须存在的,异常表的格式如下。
名称 |
类型 |
数量 |
start_pc |
u2 |
1 |
end_pc |
u2 |
1 |
handler_pc |
u2 |
1 |
catch_type |
u2 |
1 |
- 如果字节码从第 start_pc 到 end_pc 行之间(不包含第 end_pc)行出现了类型为 catch_type 或其子类的异常(catch_type 为指向一个 CONSTANT_Class_info 型常量的索引),则转到第 handler_pc 行继续处理。当 catch_type 的值为 0 时,代表任何的异常情况都需要转向到 handler_pc 行进行处理。
- 异常表实际上是 Java 代码的一部分,编译器使用异常表而不是简单的跳转命令来实现 Java 异常及 finally 处理机制。
- 字节码的 " 行 " 是一种形象的描述,指的是字节码相对于方法体开始的偏移量,而不是 Java 源代码的行号。
1.10.2 ConstantValue 属性
- 该属性为一个定长属性,用来通知虚拟机自动为静态变量赋值。
- 只有被 static 关键字修饰的变量才可以使用这项属性。
- 对于非 static 类型的变量(也就是实例变量)的赋值是在 实例构造器方法 中进行的。
- 对于类变量,则有两种式可以选择。
- 赋值在类构造器方法中进行和使用 ConstantValue 属性来赋值。
- 目前 Sun Javac 编译器是,如果同时使用 final 和 static 来修饰一个变量,并且这个变量的数据类型是基本类型或 java.lang.String 的话,就生成 ConstantValue 属性来进行初始化,如果这个变量没有被 final 修饰,或者并非基本类型或字符串,则选择在类构造器中进行初始化。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
constantValue_index |
u2 |
1 |
- ConstantValue 属性是一个定长属性,它的 attribute_length 数据项值必须为 2。
- constantvalue_index 数据项代表了常量池中一个字面常量的引用,根据字段类型不同,字面量可以是 CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info 和 CONSTANT_String_info 常量中的一种。
1.10.3 Deprecated 及 Synthetic 属性
- 两者为标志类型的布尔属性,只存在是和否的区别,没有值的概念。
- Deprecated 属性表示某个类、字段或方法不在被推荐使用。
- Synthetic 属性代表此字段或者方法并不是由 Java 源码直接产生的,而是由编译器自行添加,如 Bridge Method。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
- 其中 attribute_length 数据项的值必须为 0x00000000,因为没有任何属性值需要设置。
1.10.4 Exceptions 属性
- 这里的 Exceptions 属性是在方法表中与 Code 属性平级的一项属性,而不是 Code 属性表中的异常属性表。
- 用于列举方法中可能抛出的受查异常(throws 关键字后面列举的异常)。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
number_of_exceptions |
u2 |
1 |
exceptions_index_table |
u2 |
number_of_exceptions |
- 此属性表中的 number_of_exceptions 项表示访求可能抛出 number_of_exceptions 种受检查异常,每一种受检查异常使用一个 exception_index_table 项表示,为指向常量池中 CONSTANT_Class_info 型常量表的索引,代表了该受检查异常的类型。
1.10.5 InnerClasses 属性
- 此属性用于记录内部类与宿主类之间的关联。
- 如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成 InnerClasses 属性。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
number_of_classes |
u2 |
1 |
inner_classes |
inner_classes_info |
number_of_classes |
- 数据项 number_of_classes 代表需要记录多少个内部类信息,每一个内部类信息都由一个 inner_classes_info 表进行描述。
名称 |
类型 |
数量 |
inner_classes_info_index |
u2 |
1 |
outer_classes_info_index |
u2 |
1 |
inner_name_index |
u2 |
1 |
inner_class_access_flags |
u2 |
1 |
- inner_classes_info_index 和 outer_classes_info_index 都是指向常量池中 CONSTANT_Class_info 型常量表的索引,分别代表了内部类和宿主类的符号引用。
- inner_name_index 是指向常量池 CONSTANT_Utf8_info 型常量的索引,代表这个内部类的名称,如果是匿名内部类,那么这项值为 0。
- inner_class_access_flags 是内部类的访问标志,类似于类的 access_flags。
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
内部类是否为 public 类型。 |
ACC_PRIVATE |
0x0002 |
内部类字段是否 private。 |
ACC_PROTECTED |
0x0004 |
内部类字段是否 protected。 |
ACC_STATIC |
0x0008 |
内部类字段是否 static。 |
ACC_FINAL |
0x0010 |
内部类是否被声明为 final,只有类可设置。 |
ACC_INTERFACE |
0x0020 |
内部类标识这是一个接口。 |
ACC_ABSTRACT |
0x0400 |
内部类是否为 abstract 类型,对于接口或者抽象类来说,此标志值为真,其他类值为假。 |
ACC_SYNTHETIC |
0x1000 |
内部类是否由用户代码产生的。 |
ACC_ANNOTATION |
0x2000 |
内部类是否是一个注解。 |
ACC_ENUM |
0x4000 |
内部类是否是一个枚举。 |
1.10.6 LineNumberTable 属性
- 用于描述 Java 源代码行号与字节码行号之间的对应关系。
- 可以在 Javac 中分别使用 -g:none 或 -g:lines 选项来取消或要求生成这项信息,如果选择不生成,在程序运行抛出异常时,堆栈中将不会显示出错的行号,在调试程序时也无法按照源码行设置断点。
1.10.7 LocalVariableTable 属性
- 用于描述栈帧中局部变量表中的变量与 Java 源码中定义的变量之间的关系。
- 可以在 Javac 中分别使用 -g:none 或 -g:vars 选项来取消或要求生成这项信息,如果没有生成该属性,对程序运行没有影响,只是对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。
1.10.8 StackMapTable 属性
- 是一个复杂的变长属性,位于 Code 属性的属性表,这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
- StackMapTable 属性中包含零至多个栈映射栈(Stack Map Frames),每个栈映射帧都显示或隐式的代表了一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。
- 类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
- 一个方法的 Code 属性最多只能有一个 StackMapTable 属性,否则将抛出 ClassFormatError 异常。 StackMapTable 属性的结构如下。
名称 |
类型 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
number_of_entries |
u2 |
1 |
stack_map_frame_entries |
stack_map_frame |
number_of_entries |
1.10.9 SourceFile 属性
- 该属性是一个定长属性,用于记录生成这个 Class 文件的源码文件名称。
类型 |
名称 |
数量 |
attribute_name_index |
u2 |
1 |
attribute_length |
u4 |
1 |
sourcefile_index |
u2 |
1 |
- 可以使用 Javac 的 -g:none 或 -g:source 选项来关闭或要求生成这项信息,如果不生成这项属性,当抛出异常时堆栈中将不会显示出错误代码所属的文件名。
参考资料
https://blog.csdn.net/u012998254/article/details/82802627
https://www.jianshu.com/p/846f93699663
https://www.cnblogs.com/zawier/p/6659311.html
网友评论