26-class文件结构
一、概述
- Java虚拟机不与任何语言绑定,它只与“Class文件”这样特定的二进制文件格式所关联。无论使用何种语言进行软件开发,只要能将源文件编译为正确的 Class文件,那么这种语言就可以在Java虚拟机上执行。可以说,统一而强大的Class文件结构,就是Java虚拟机的基石、桥梁
- 所有的JVM全部遵守Java虚拟机规范,也就是所有的JVM环境都是一样的,这样一来字节码文件可以在各种JVM上运行
JVM规范的字节码
- “前端编译器”的主要任务就是负责将符号Java语法规范的Java代码转换为符号JVM规范的字节码文件
- javac是一种能够将Java源码编译为字节码的前端编译器
-
javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析、语法解析、语义解析、生成字节码
image.png
image.png
二、Java的前端编译器
- Java源代码的编译结果就是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要任务的就是javac编译器。javac是一种能够将Java源码编译为字节码的“前端编译器”
- HotSpotVM并没有强制要求前端编译器只能使用javac来编译字节码,只要编译结果符合JVM规范都可以被JVM所识别即可
三、通过字节码指令了解代码细节
- 代码
/**
* 字节码开代码细节
*/
public class IntegerClassTest {
public static void main(String[] args) {
Integer r1 = 5;
int r2 = 5;
System.out.println(r1 == r2);
Integer r3 = 10;
Integer r4 = 10;
System.out.println(r3 == r4);
Integer r5 = 128;
Integer r6 = 128;
System.out.println(r5 == r6);
}
}
- 打印输出
true
true
false
- 思考:
- 1、输出结果是否如你所愿?
- 2、r1与r2类型不同为何相同
- 3、r5与r6类型相同赋值也相同,为何不等
- IntegerClassTest.class image.png
- 从字节码可以看到r1、r2虽然同样赋值为5,但是在字节码层面上却不同,r1先取出5来并且执行了java/lang/Integer.valueof方法
- java/lang/Integer.valueof源码
/**
* int类型的值 -> Integer类型
* static final int low = -128;
* static final int high = 127;
* static final Integer cache[]:range [-128, 127] must be interned (JLS7 5.1.7)
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
- IntegerCache源码
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
- 通过查看 IntegerCache 源码可知
- 1、low = -128
- 2、high = 127
- cache:range [-128, 127]
- 小结
- 在int类型转Integer类型时会自动触发valueOf方法,如果int类型的值在cache:range [-128, 127]范围内,直接从cache数组中返回相应的值,r1 = 5就在这个cache范围内,所以直接把cache中对应的Integer类型的5返回给r1;r5和r6赋值为128超出了cache范围,直接使用new Integer(i)返回结果,因为每次都是new的新对象,所以r5 != r6
四、Class文件
1、字节码文件里是什么?
- 字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码
2、什么是字节码指令(byte code)
- Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的“操作码(opcode)”以及跟随其后的零至多个代表此操作所需参数的“操作数(operand)”所构成的。虚拟机中也有许多指令并不包含操作数,只有一个操作码
0 iconst_5
1 invokestatic #2 <java/lang/Integer.valueOf>
4 astore_1
5 iconst_5
6 istore_2 // 只有操作码,不包含操作数
7 getstatic #3 <java/lang/System.out> // 既有操作码,又有操作数
10 aload_1
11 invokevirtual #4 <java/lang/Integer.intValue>
14 iload_2
15 if_icmpne 22 (+7)
3、解读供虚拟机解释执行的二进制字节码 image.png
4、Class类的本质
- 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,Class文件实际上它并不一定以磁盘文件的形式存在。Class文件是一组以8位字节码为基础单位的二进制流
5、Class文件格式
- Class的结构不像XML等描述语言,由于它没有任何分隔符号。所以在其中的数据项,无论是字节顺序还是数量,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如果,都不允许改变
- Class文件格式采用一种类似于 C 语言结构体的方式进行数据存储,这种结构中只有两种数据类型:“无符号数”和“表”
- 无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值或安装UTF-8编码构成字符串值
- 表:是有多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上就是一张表。由于表没有固定长度,所以通常会在其签名加上个数说明
- 代码
/**
* Class文件结构
*/
public class ClassDemoTest {
private int num = 1;
public int add() {
num += 2;
return num;
}
}
- 字节码文件 image.png
6、Class文件结构概述
- Class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对Class文件结构做出一些调整,但是其基本结构和框架时非常稳定的
- Class文件的总体结构如下:
- 1、魔数
- 2、Class文件版本
- 3、常量池
- 4、访问标志
- 5、类索引,父类索引,接口索引集合
- 6、字段表集合
- 7、方法表集合
-
8、属性表集合
ClassFile.png
image.png
- 解读Class文件二进制 image.png
7、Magic Number 魔数
- 每个 Class 文件开头的 4 个字节的无符号整数称为魔数
- 魔数的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的 Class 文件。即:魔数是 Class 文件的标识符
- 魔数值固定为0xCAFEBABE,不会改变
- 如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验的时候就会直接抛出错误 image.png
- 使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动
8、版本号
- 紧接着魔数的 4 个字节存储的是 Class 文件的版本号。同样也是4个字节。第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version
- 副版本号和主版本号共同构成Class文件的格式版本号。如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个 Class 文件的格式版本号就确定为 M.m
-
版本号和Java编译器的对应关系如下:
image.png
image.png - Java 的版本号是从 45 开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1
- 不同版本的Java编译器编译的 Class 文件对应的版本是不一样的。目前,高版本的Java虚拟机可以执行由低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行由高版本编译器生成的 Class 文件。否则 JVM 会抛出 java.lang.UnsupportedClassVersionError异常
9、常量池:存放所有常量
- 常量池是 Class 文件中内存最为丰富的区域之一。常量池对于 Class 文件中的字段和方法解析也有着至关重要的作用
-
常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值(constant_pool_count)。与Java语言不同的是,这个容量计数式从1而不是从0开始
image.png
image.png - Class文件使用了一个前置的容量计数器(constant_pool_count)加若干个连续的数据项(constant_pool)的形式来描述常量池内容。我们把这一系列连续常量池数据成为常量池集合
- 常量池表项中,用于存放编译时期生成的各种“字面量”和“符号引用”
1、常量池计数器(constant_pool_count)
- 由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值
- 常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即constant_pool_count=1表示常量池中有0个常量
2、常量池表(constant_pool [])
- constant_pool是一种表结构,以 1~constant_pool_count - 1为索引。表明了后面有多个常量项。
- 常量池主要存放两个类常量:字面量(Literal) 和 符号引用(Symbolic References)
- 包含了Class文件结构及其子结构中引用的所有字符串常量、类或接口、字段名和其他常量。常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节成为 tag byte (标记字节、标签字节) image.png
10、字面量和符号引用
- 常量池主要存放两个类常量:字面量(Literal) 和 符号引用(Symbolic References) image.png
1、全限定名
- com/lkty/integerclass/ClassDemoTest这个就是类的全限定名,仅仅是把包名“.”替换成“/”,为了使连续的多个全限定名之间不产生混淆,在使用时最后一班会加入一个“;”表示全限定名结束
2、简单名称
- 是指没有类型和参数修饰的方法或者字典名称,上面代码中的类的add()方法和num字段的简单名称分别是add和num
3、描述符
- 描述符的作用是用来描述字段的数据类型、方法的参数列表(数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示 image.png
- 用描述符来描述方式时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“()”之内。如方法java.lang.String toString()的描述符为() Ljava/lang/String;方法int abc(int[] x, int y)的描述符为([II)
- 虚拟机在加载Class文件时才会进行动态链接,也就是说,Class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中
- 符号引用和直接引用的区别与关联
- 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。“符号引用与虚拟机实现的内存布局无关”,引用的目标并不一定已经加载到了内存中
- 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了
4、常量类型和结构
-
常量池中每一项常量都是一个表,JDK7之后共有 14 种不同的表结构数据
image.png
image.png - 根据上图每个类型的描述可以知道每个类型是用来描述常量池中哪些内容(主要是字面量、符号引用)的。如:CONSTANT_Integer_info是用来描述常量池中字面量信息的,而且只是整型字面量信息
- 标志为15、16、18的常量项类型是用来支持动态语言调用的(JDK7时加入)
5、总结1
- 这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1类型的标志位(tag),代表当前这个常量项使用的是那种表结构,即哪种常量类型
- 在常量池列表中,CONSTANT_Utf8_info常量项是一种使用改进过的UTF-8编码格式来存储如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符串信息
- 这14种常量项结构还有一个特点是,其中13个常量项占用的字节固定,只有CONSTANT_Utf8_info占用字节不固定,其大小由length决定。为什么?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,大小不固定,编译后,通过utf-8编码,就可以知道其长度
6、总结2
- 常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一
- 常量池中为什么要包含这些内容?Java代码在进行javac变异的时候,并不像 C 和 C++ 那样有“链接”这一步,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池中获取对应的符号应用,在再类创建时或运行时解析、翻译到具体的内存地址之中。
11、访问标识(access_flag、访问标志、访问标记)
- 在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;是否声明为final等 image.png
- 类的访问权限通常为ACC_开头的常量
- 每一种类型的表示都是通过设置访问标记的32位中特定为来实现的。如public final的类,则该标记为ACC_PUBLIC | ACC_FINAL
- 使用ACC_SUPER可以让类更准确地定位到父类的方法super.method(),现代编译器都会设置并且使用这个标记
- 带有ACC_INTERFACE标志的class文件表示的是接口而不是类,反之则表示的是类而不是接口
- 如果一个class文件被设置了 ACC_INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志。同时它不能再设置ACC_FINAL、ACC_SUPER 或 ACC_ENUM 标志
- 如果没有设置 ACC_INTERFACE 标志,那么这个class文件可以具有商标中除 ACC_ANNOTATION 外的其他所有标志。当然,ACC_FINAL和ACC_ABSTRACT这类互斥的标志除外。这两个标志不得同时设置
- ACC_SUPER标志用于确定类或接口里面的invokespecial指令使用的是哪一种执行语义。“针对Java虚拟机指令集的编译器都应当设置这个标志”。对于JavaSE8及后续版本来说,无论class文件中这个标志的实际值是什么,也不管class文件的版本号是多少,Java虚拟机都认为每个class文件均设置了ACC_SUPER标志
- ACC_SUPER标志是为了向后兼容由旧Java编译器所编译的代码而设计的。目前的 ACC_SUPER标志在有JDK1.0.2之前的编译器所生成的access_flags中是没有确定含义的,如果设置了该标志,那么Oracle的Java虚拟机实现会将其忽略
- ACC_SYNTHETIC 标志意味着该类或接口是由编译器生成的,而不是有源代码生成的
- 主键类型必须设置ACC_ANNOTATION 标志。如果设置 ACC_ANNOTATION 标志,那么也必须设置ACC_INTERFACE标志
- ACC_ENUM标志标明该类或其父类为枚举类型
12、类索引、父类索引、接口索引集合
-
在访问标记后,会指定该类的类别、父类类别以及实现的接口
image.png - 这三项数据来确定这个类的继承关系
- 类索引用于确定这个类的全限定名
- 父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.object之外,所有的Java类都有父类,因此除了java.lang.object 外,所有Java类的父类索引都不为0
- 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends 语句)后的接口顺序从左到右排列在接口索引集合中
1、this_class(类索引)
- 2字节无符号整数,指向常量池的索引。它提供了类的全限定名,如
com/lkty/integerclass/ClassDemoTest。this_class的值必须是对常量池表中某项的一个有效索引值。常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构体,该结构体表示这个class文件所定义的类或接口
2、super_class(父类索引)
- 2字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名。如果我们没有继承任何类,其默认继承的是java/lang/object类。同时,由于Java不支持多继承,所以其父类只有一个
- superclass指向的父类不能是final
3、Interface
- 指向常量池索引集合,它提供了一个符号引用到所有已实现的接口
- 由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class(当然这里就必须是接口,而不是类)
- interfaces_count(接口计数器):表示当前类或接口的直接超接口数量
- interface[](接口索引集合):interface[]中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为interfaces_count。每个成员interface[i]必须为 CONSTANT_Class_info结构,其中0 <= i < interfaces_count。在interface[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左到右)一样。
13、字段表集合
1、fields
- 用于描述接口或类中声明的变量。字段(field)包括“类级别变量以及实例级别变量”,但是不包括方法内部、代码块内部什么的局部变量
- 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述
- 它指向常量池索引集合,他描述了每个字段的完整信息。如:字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等
- 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,担忧可能列出原本Java代码之中不存在的字段。如在内部类种中为了保存对外部类的访问性,会自动添加指向外部类实例的字段
- 在Java语言中字段时无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名也是合法的
2、fields_count(字段计数器)
- fields_count的值表示当前class文件fields表的成员个数。使用2个字节表示
- fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的哪些字段
3、fields[](字段表)
- fields表汇总的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述
- 一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是bool值,要么有,要么没有
- 作用域(public、private、protected修饰符)
- 实例变量还是类变量(static修饰符)
- 可变性(final)
- 并发可见性(volatile修饰符,是否强制从主内存读写)
- 可否序列化(transient修饰符)
- 字段数据类型(基本数据类型、对象、数组)
- 字段名称
- 字段表结构:字段表作为一个表,同样有他自己的结构 image.png
4、字段表访问标识 image.png
5、字段名索引
- 根据字段名索引的值,查询常量池中的指定索引项即可
6、描述符索引
- 描述符的作用是用来描述字段的数据类型、方法的参数列表(数量、类型以及顺序)和返回值。 image.png
7、属性表集合
- 一个字段还可以拥有一些属性,用于存储更多的额外信息。如:初始化值、一些注释信息等。属性个数存放在attribute_count中,属性具体内容存放在attributes数组中
14、方法表集合
-
methods:指向常量池索引集合,它完整描述了每个方法的签名
-
在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。如:方法的访问修饰符(public、private、protected),方法的返回值类型以及方法的参数信息等
-
如果这个方法不是抽象的或者不是native的,那么字节码钟会体现出来
-
methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,methods表有可能会出现有编译器自动添加的方法,最典型的便是编译器产生的方法信息(如:类(接口)初始化方法<clinit>()和实例初始化方法<init>())
-
注意事项:在Java中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同俩对一个已有方法进行重载。
-
注意事项:在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法由相同的名称和特征签名,但返回值不同,那么也是可以合法共存与同一个class文件中
-
尽管Java语法规范并不允许一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间返回值不能相同
1、methods_count(方法计数器)
- methods_count的值表示当前class文件methods表的成员个数,使用2个字节来表示
- methods表中每个成员都是一个methods_info结构
2、methods
- methods表中的每个成员都必须是一个methods_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个methods_info结构的access_flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令
-
methods_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法
image.png
3、方法表访问标志
- 与字段表一样,方法表也有访问标志,而且他们的标志有部分相同
15、属性表集合(attributes)
- 方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该 class 文件的源文件的名称。以及任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试。
- 字段表、方法表都可以有自己的属性表,用于描述某些场景专有的信息。属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性
1、attributes_count(属性计数器)
- attributes_count的值表示当前class文件属性表的成员个数。属性表中每一项都是一个attributes_info结构
2、attributes [] (属性表)
- 属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活,各种不同的属性只要满足一下结构即可
3、属性的通用格式
- 只需说明属性的名称以及占用位数的长度即可,属性表具体的结构可以去自定义 属性的通用格式.png
4、属性类型
-
属性表实际上可以有很多类型,看到的Code属性只是其中一种,Java8里面定义了23种属性
image.png
image.png - 官网 image.png
15、部分属性详解
1、ConstantValue 属性
- ConstantValue 属性表示一个常量字段的值。位于field_info结构的属性表中
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index; // 字段值在常量池中的索引,常量池在该索引处的项给出该属性表示的常量值
}
2、Deprecated 属性
- Deprecated 属性是在JDK1.1为了支持注解中的关键@deprecate而引入的
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
3、Code 属性
- Code属性就是存放方法体里面的代码。但是,并非所有方法表都有Code属性。像接口或者抽象方法,他们没有具体的方法体,因此也就不会有Code属性了 image.png
- Code属性表的前两项跟属性表是一致的,即Code属性表遵循属性表的结构,后面那些则是他的自定义的结构
4、InnerClasses 属性
- 为了方便说明特别定义一个表示类或接口的 Class 格式为 C。如果 C 的常量池中包含某个CONSTANT_Class_info成员,且这个成员所表示的类或接口不属于任何一个包,那么 C 的ClassFile 结构的属性表中就必须含有对应的 InnerClass 属性。
5、LineNumberTable 属性
- LineNumberTable 属性是可选变长属性,位于 Code 结构的属性表
- LineNumberTable 属性是用来描述Java源码行号与字节码行号之间的对应关系。这个属性可以用来在调试的时候定位代码执行的行数
- start_pc:字节码行号
- line_number:Java源代码行号
- 在Code属性的属性表中,LineNumberTable 属性可以按照任意顺序出现,此外多个LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即LineNumberTable 属性不需要与源文件的行一一对应
-
LineNumberTable 属性表结构
image.png
6、LocalVariableTable 属性
- LocalVariableTable 是可选变长属性,位于 Code 属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息。在 Code 属性的属性表中,LocalVariableTable 属性可以按照任意顺序出现。Code 属性中的每个局部变量最多只能有一个 LocalVariableTable 属性
- start pc + length:表示这个变量在字节码中生命周期起始和结束的偏移位置(this生命周期从0到尾)
- idnex:就是这个变量在局部变量表中的槽位(槽位可复用)
- name:就是变量名称
- Descriptor:局部变量类型描述
- LocalVariableTable 属性表结构 image.png
7、Signature 属性
- Signature 属性是可选的定长属性,位于 ClassFile、field_info或 method_info结构的属性表中。在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature 属性会为它记录泛型签名信息
8、SourceFile 属性
image.png9、其他属性
- Java虚拟机中预定义的属性有20多个,这里就不一一介绍了