共计 13314 个字符,预计需要花费 34 分钟才能阅读完成。
字节码文件概述
字节码文件的跨平台性
Java 语言:跨平台的语言
- 当 Java 源代码胜利编译字节码后,如果想在不同的平台下面运行,则无需再次编译
- 这个劣势目前来说曾经不再吸引人,因为 Python、PHP、Ruby、Lisp 等有弱小的解释器
- 跨平台曾经快成为一门语言的必选个性
Java 虚拟机:跨语言的平台
Java 虚拟机不和包含 Java 在内的任何语言绑定,它只与 Class 文件这种特定的二进制文件所关联
,无论应用何种语言进行软件开发,只有能将源文件编译为正确的 Class 文件,那么这种语言就能够这 Java 虚拟机上执行。
想要让一个 Java 程序正确的运行在 JVM 中,Java 源码就必须要被编译为合乎 JVM 标准的字节码。
- 前端编译器的次要工作就是负责将合乎 Java 语法标准的 Java 代码转换为合乎 JVM 标准的字节码文件
- javac 是一种可能将 Java 源码编译为字节码的前端编译器
- javac 编译器这将 Java 源码编译为一个无效的字节码文件过程中经验了 4 个步骤,别离是
词法解析、语法解析、语义解析以及生成字节码
Java 的编译
-
前端编译
Java 源代码的编译后果是字节码,那么必定要有一种可能将 Java 源代码编译为字节码,承当这个责任的就是一配置在 path 环境变量中的
javac 编译器
。javac 是一种可能将 Java 源码编译为字节码的前端编译器
。长处:
- 许多 Java 语法新个性(泛型、外部类等),是靠前端编译器实现的,而不是依赖虚拟机。
- 编译成的 Class 文件能够间接给 JVM 解释器解释执行,省去编译工夫,放慢启动速度。
毛病:
- 对代码运行效率简直没有任何优化措施。
- 解释执行效率较低,所以须要联合上面的 JIT 编译。
-
后端编译 /JIT 编译
通过 Java 虚拟机(JVM)内置的即时编译器(Just In Time Compiler,JIT 编译器);在运行时把 Class 文件字节码编译成本地机器码的过程。
长处:
- 通过在运行时收集监控信息,把 ” 热点代码 ”(Hot Spot Code)编译成与本地平台相干的机器码,并进行各种档次的优化。
- 能够大大提高执行效率。
毛病:
- 收集监控信息影响程序运行。
- 编译过程占用程序运行工夫。
- 编译机器码占用内存。
-
动态提前编译(AOT)
程序运行前,间接把 Java 源码文件编译成本地机器码的过程。
长处:
- 编译不占用运行工夫,能够做一些较耗时的优化,并可放慢程序启动。
- 把编译的本地机器码保留磁盘,不占用内存,并可屡次应用。
毛病:
- 因为 Java 语言的动态性(如反射)带来了额定的复杂性,影响了动态编译代码的品质,个别动态编译不如 JIT 编译的品质,这种形式用得比拟少。
目前 Java 体系中次要还是采纳前端编译 +JIT 编译的形式
运作过程:
- 首先通过前端编译把合乎 Java 语言标准的程序代码转化为满足 JVM 标准所要求 Class 格局。
- 而后程序启动时 Class 格式文件发挥作用,解释执行,省去编译工夫,放慢启动速度。
- 针对 Class 解释执行效率低的问题,在运行中收集性能监控信息,得悉 ” 热点代码 ”。
- JIT 逐步发挥作用,把越来越多的热点代码 ” 编译优化老本地代码,进步执行效率。
透过字节码指令看代码细节
面试题
- 类文件构造有几个局部?
- 晓得字节码吗?字节码都有哪些?Integer x = 5; int y = 5; 比拟 x == y 都经验哪些步骤?
首先在申明 Integer x 的时候会调用 Integer.valueOf 办法,首先看下这个办法
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如果所传入的值是 -128~127 之间,就只用动态外部类的 cache 数组,否则就 new 一个新的对象。
这也表明为什么 Integer x = 128; Integer y = 128; x != y 的起因。
之后能够看到调用了 Integer.intValue 进行主动拆箱操作,使得 x 变成 int 类型进行比拟。
虚拟机的基石:Class 文件
- 字节码文件里是什么?
源代码通过编译器编译之后便生成一个字节码文件,字节码是一种二进制的类文件,它的内容是 JVM 的指令,而不像 C、C++ 经由编译器间接生成
机器码
。 - 什么是字节码指令?
Java 虚拟机的指令,由一个字节长度的、代表着某种特定操作含意的
操作码
(opcode),以及追随其后的零至多个代表此操作所需参数的操作数
(operand) 所形成。虚拟机中许多指令并不蕴含操作数,只有一个操作码。比方 aload_1 (操作码)、aload 4(操作码 + 操作数),因为 aload 只有 0、1、2、3 所以如果想持续裁减,就要用到操作数。
Class 文件构造
- Class 类的实质
因为一个 Class 文件都对应着惟一一个类或接口的定义信息,但反过来说,Class 文件实际上它并不一定以磁盘文件模式存在。Class 文件是一组以 8 位字节为根底单位的
二进制流
。 -
Class 文件格式
Class 的构造不像 XML 等描述语言,因为它没有任何宰割符号,所以这其中的数据项,无论是字节程序还是数量,都是被严格限定的,哪个字节代表什么含意、长度多少、先后顺序,都不容许扭转。
Class 文件格式采纳一种相似于 C 语言构造体的形式进行数据存储,这种构造中只有两种数据类型:
无符号数
和表
。- 无符号数属于根本数据类型,以 u1、u2、u4、u8 来别离代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数能够用来形容数字、索引援用、数量值或者依照 UTF- 8 编码形成字符串值。
- 表是由多个无符号数或者其它表作为数据项形成的复合数据类型,所有表都习惯性地以 ”_info” 结尾。表用于形容有档次关系的复合构造的数据,整个 Class 文件实质就是一张表,因为表没有固定长度,所以通常会其后面加上个数阐明。
-
Class 文件构造概述
Class 文件的构造并不是变化无穷的,随着 Java 虚拟机的一直倒退,总是不可避免地会对 Class 文件构造做出一些调整,然而根本构造和框架是十分稳固的。
构造如下:
- 魔数
- Class 文件版本
- 常量池
- 拜访标记
- 类索引,父类索引,接口索引汇合
- 字段表汇合
- 办法表汇合
- 属性表汇合
| 类型 | 名称 | 阐明 | 长度 | 数量 |
| ————– | ——————- | ———————- | ——- | ——————— |
| u4 | magic | 魔数, 辨认 Class 文件格式 | 4 个字节 | 1 |
| u2 | minor_version | 副版本号(小版本) | 2 个字节 | 1 |
| u2 | major_version | 主版本号(大版本) | 2 个字节 | 1 |
| u2 | constant_pool_count | 常量池计数器 | 2 个字节 | 1 |
| cp_info | constant_pool | 常量池表 | n 个字节 | constant_pool_count-1 |
| u2 | access_flags | 拜访标识 | 2 个字节 | 1 |
| u2 | this_class | 类索引 | 2 个字节 | 1 |
| u2 | super_class | 父类索引 | 2 个字节 | 1 |
| u2 | interfaces_count | 接口计数器 | 2 个字节 | 1 |
| u2 | interfaces | 接口索引汇合 | 2 个字节 | interfaces_count |
| u2 | fields_count | 字段计数器 | 2 个字节 | 1 |
| field_info | fields | 字段表 | n 个字节 | fields_count |
| u2 | methods_count | 办法计数器 | 2 个字节 | 1 |
| method_info | methods | 办法表 | n 个字节 | methods_count |
| u2 | attributes_count | 属性计数器 | 2 个字节 | 1 |
| attribute_info | attributes | 属性表 | n 个字节 | attributes_count |
字节码文件解析
首先咱们创立一个简略的源码:
public class Demo {
private int num = 1;
public int add() {
num = num + 2;
return num;
}
}
通过 javac 命令编译后能够看到 Class 文件内容:
public class Demo {
private int num = 1;
public Demo() {}
public int add() {
this.num += 2;
return this.num;
}
}
为什么编译当前减少了 无参结构器
,以及this 关键字
,咱们一点点剖析。
之后咱们应用 Notepad++
, 须要装置一个HEX-Editor
插件来关上这个 Class 文件就能够看到下图内容。
这里就间接应用 excel 来清晰解释具体内容。
魔数:Class 文件的标记
- 每个 Class 文件结尾的 4 个字节的无符号整数成为魔数(Magic Number)
- 它的惟一作用就是确定这个文件是否为一个能被虚拟机承受的无效非法的 Class 文件。即:魔数是 Class 文件的标识符。
- 魔数值固定为 0xCAFEBABE
- 如果一个 Class 文件不以 0xCAFEBABE 结尾,虚拟机在进行文件校验的时候就会间接抛出 ClassFormatError 谬误。
-
应用魔数而不是扩展名来辨认次要是基于平安方面思考,因为文件扩展名是能够随便扭转的。
Class 文件版本号
- 紧接着魔数的 4 个字节是 Class 文件的版本号。同样也是 4 个字节。第 5 个和第 6 个字节所代表的的含意就是编译的副版本号 minor_version,而第 7 个和第 8 个字节就是编译的主版本号 major_version。
- 它们独特形成了 Class 文件的格局版本号。譬如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个 Class 文件的格局版本号就是 M.m。
- 版本号和 Java 编译器的对应关系表如下:
| 主版本(十进制)| 副版本(十进制)| 编译器版本 |
| —————- | —————- | ———- |
| 45 | 3 | 1.1 |
| 46 | 0 | 1.2 |
| 47 | 0 | 1.3 |
| 48 | 0 | 1.4 |
| 49 | 0 | 1.5 |
| 50 | 0 | 1.6 |
| 51 | 0 | 1.7 |
| 52 | 0 | 1.8 |
| 53 | 0 | 1.9 |
| 54 | 0 | 1.10 |
| 55 | 0 | 1.11 | - Java 的版本号是从 45 开始的,JDK 1.1 之后的每个 JDK 大版本公布主版本号向上加 1。
- 不同版本的 Java 编译器编译的 Class 文件对应的版本是不一样的。目前,高版本的 Java 虚拟机能够执行低版本编译器编译的 Class 文件,然而低版本的 Java 虚拟机不能执行由高版本编译器生成的 Class 文件。否则 JVM 会抛出
java.lang.UnsupportedClassVersionError
异样。
常量池:寄存所有常量
- 常量池是 Class 文件中内容最为丰盛的区域之一。常量池对于 Class 文件中的字段和办法解析有着至关重要的作用。
- 随着 Java 虚拟机的一直倒退,常量池的内容也日渐丰盛。能够说,常量池是整个 Class 文件的基石。
- 在版本号之后,紧跟着的就是常量池的数量,以及若干个常量池表项。
- 常量池中常量的数量是不固定的,所以须要一个 u2 类型的无符号数,代表着常量池容量计数器(constant_pool_count),与 Java 语言习惯不一样的是,容量计数器是从 1 开始而不是 0。
常量池表项
中,用于寄存编译期生成的各种字面量
和符号援用
,这部分内容将在类加载后进入办法区的运行时常量池
中寄存。
常量池计数器
- 因为常量池的数量不固定,所以须要搁置两个字节来示意常量池容量计数器。
- 常量池计数器(u2 类型):从 1 开始,示意常量池中有多少项常量。即 constant_pool_count = 1 示意常量池中有 0 个常量池。
-
咱们看方才举例的 Demo:其值为 0x0016,转换为十进制也就是 22。
然而理论中只有 21 项常量,范畴是 1 -21。
这里的常量池把第 0 项空进去了,为了满足前面某些指向常量池的索引值的数据在特定状况下须要表白 ” 不援用任何一个常量池项 ” 的含意,这种状况能够应用索引 0 来示意。
常量池表
- constant_pool 是一种表及构造,以 1 ~ constant_pool_count – 1 为索引。表明前面有多少个常量项。
- 常量池次要寄存放两大类变量:
字面量(Literal)
和符号援用(Symbolic Reference)
。 - 它蕴含了 Class 文件构造及其子结构中援用的所有字符串常量、类、或接口名、字段名、和其它常量。常量池中的每一项都具备雷同特色。第 1 个字节作为类型标记,用于确定该项的格局,这个字节成为 tag byte(标记字节)。
字面量和符号援用
常量池次要寄存放两大类变量:字面量(Literal)
和 符号援用(Symbolic Reference)
。
常量 | 具体的常量 |
---|---|
字面量 | 文本字符串 |
申明为 final 的常量值 | |
符号援用 | 类和接口的全限定名 |
字段和名称的描述符 | |
办法的名称和描述符 |
全限定名
com/test/Demo 这个就是类的全限定名,仅仅是把包名的 ”.” 替换成了 ”/”,为了使间断的多个全限定名之间不产生混同,在应用时最初个别会退出一个 ”;” 示意全限定名完结。
简略名称
简略名称是指没有类型和参数润饰的办法或者字段名称,下面例子中的类的 add()办法,和 num 字段的简略名称别离是 add 和 num。
描述符
描述符的作用是用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值。
依据描述符规定,根本数据类型(boolean,byte,char,short,int,float,long,double)以及代表无返回值的 void 类型都用一个大写字符来示意,而对象类型则用字符 L 加对象的全限定名来示意:
标志符 | 含意 |
---|---|
B | 根本数据类型 byte |
C | 根本数据类型 char |
D | 根本数据类型 double |
F | 根本数据类型 float |
I | 根本数据类型 int |
J | 根本数据类型 long |
S | 根本数据类型 short |
Z | 根本数据类型 boolean |
V | 代表 void 类型 |
L | 对象类型,比方:Ljava/lang/Object; |
[ | 数组类型,代表一维数组。比方:double[][][] = [[[D |
public static void main(String[] args) {Object[] arr = new Object[10];
System.out.println(arr);//[Ljava.lang.Object;@14ae5a5
Long[][] longs = new Long[10][10];
System.out.println(longs);//[[Ljava.lang.Long;@7f31245a
int[][] ints = new int[10][10];
System.out.println(ints);//[[I@7f31245a}
须要留神的是,用描述符来形容办法的时候,先参数列表后返回值的程序形容,参数列表依照参数的严格程序放在一组小括号 "()" 内。
虚拟机在加载 Class 文件时才会进行动静链接,也就是说,Class 文件中不会保留各个办法和字段的最终内存布局信息,因而,这些字段和办法的符号援用不通过转换是无奈间接被虚拟机应用的。当虚拟机运行时,须要从常量池中取得对应的符号援用,再在类加载过程中的解析阶段将其替换为间接援用,并翻译到具体的内存地址中
。
- 符号援用 :符号援用以
一组符号
来形容所援用的指标,符号能够是任何模式的字面量,只有应用时无歧义地定位到指标即可。符号援用与虚拟机实现的内存布局无关
,援用的指标并不一定曾经加载到了内存中。 - 间接援用:间接援用能够是间接
指向指标的指针、绝对偏移量或是一个能间接定位到指标的句柄。间接援用是与虚拟机实现的内存布局相干的
,同一个符号援用在不同虚拟机实例上翻译进去个别不会雷同。如果有了间接援用,则阐明援用的指标必然存在内存之中。
常量类型和构造
类型 | 标记(或标识) | 形容 |
---|---|---|
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 | 示意一个动静办法调用点 |
从下面的图中能够看到,尽管每一项的构造都不雷同,然而它们有个共同点,就是每一项的第一个字节都是一个标记位,标识这一项是哪种类型的常量。
拜访标记(access_flag)
-
在常量池后,紧接着拜访标记,该标记应用两个字节示意,用于辨认一些类或者接口档次的访问信息,包含:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被申明为 final 等,具体阐明如下:
标记名称 标记值 含意 ACC_PUBLIC 0x0001 标记为 public 类型 ACC_FINAL 0x0010 标记被申明为 final,只有类能够设置 ACC_SUPER 0x0020 标记容许应用 invokespecial 字节码指令的新语义,JDK1.0.2 之后编译进去的类的这个标记默认为真。(应用加强的办法调用父类办法) ACC_INTERFACE 0x0200 标记这是一个接口 ACC_ABSTRACT 0x0400 是否为 abstract 类型,对于接口或者抽象类来说,次标记值为真,其余类型为假 ACC_SYNTHETIC 0x1000 标记此类并非由用户代码产生(即:由编译器产生的类,没有源码对应) ACC_ANNOTATION 0x2000 标记这是一个注解 ACC_ENUM 0x4000 标记这是一个枚举 - 类的拜访权限通常为 ACC_ 结尾的常量。
- 每一种类型的示意都是通过设置拜访标记的 32 位中的特定位来实现的,比方如果是 public final 的类,则标记为 ACC_PUBLIC | ACC_FINAL。
- 应用 ACC_SUPER 能够让类更精确的定位到父类的办法 super.method(),默认都是设置并应用这个标记。
咱们能够看到下面的 Demo 的字节码对应的拜访标记是 21,也就是对应表格中的 ACC_PUBLIC 和 ACC_SUPER 加起来就等于 21。
类索引、父类索引、接口索引汇合
长度 | 含意 |
---|---|
u2 | this_class |
u2 | super_class |
u2 | interfaces_count |
u2 | interfaces[interfaces_count] |
- 类索引用于确定这个类的继承关系。
- 父类索引用于确定这个类的父类全限定名,因为 Java 语言不容许多重继承,所以父类索引只有一个,除了 Object 之外,所有的 Java 类都有父类,因而除了 Object 之外,所有 Java 类的父类索引都不为 0。
- 接口索引汇合就用来形容这个类实现了拿些接口,这些被实现的接口将按 implements 语句(如果以后类自身是一个接口,则该当是 extends 语句)后的接口程序从左到右排列在接口索引汇合中。
this_class(类索引)
2 字节无符号整数,指向常量池的索引。它提供了类的全限定名,如 com/test/Demo。this_class 的值必须是常量池中某项的一个无效索引值。常量池在这个索引出的成员必须为 constant_class_info 类型构造体,该构造示意这个 Class 文件所定义的类或者接口。
super_class(父类索引)
- 2 字节无符号整数,指向常量池的索引。它提供了以后类的父类全限定名。如果咱们没有继承任何类,其默认继承的是 Java/lang/Object 类。同时,因为 Java 不反对多继承,所以其父类只有一个。
- super_class 指向的父类不能是 final 类型。
interfaces
- 指向常量池索引汇合,它提供了一个符号援用到所有已实现的接口。
- 因为一个类能够实现多个接口,因而须要以数组模式保留多个接口的索引,示意接口的每个索引也是一个指向常量池的 Constant_Class(指向的是接口,并不是类)。
interfaces_count(接口计数器)
interfaces_count 项的值示意以后类或者接口的间接超接口数量。
interfaces[](接口索引汇合)
interfaces[]中每个成员的值必须是对常量池表中某项索引的无效索引值,它的长度为 interfaces_count。每个成员 interfaces[i]必须为 constant_class_info 构造,其中 0 <= i < interfaces_count。在 interfaces[]中,各成员所示意的接口程序对应的源代码中给定的接口程序(从左至右)一样,即 interface[0]对应的是源代码中最右边的接口。
字段表汇合
- 用于形容接口或类中申明的变量。字段(field)包含
类级变量以及实例变量
,然而不包含办法外部、代码块外部申明的局部变量。 - 字段叫什么名字,字段被定义为什么数据类型,这些都是无奈固定的。只能援用常量池中的常量来形容。
- 它指向常量池索引汇合,它形容了每个字段的残缺信息。比方
字段的标识符、拜访修饰符(public、private 或 protected)、是类变量还是实例变量(static 修饰符)、是否是常量(final 修饰符)
等。
留神:
- 字段表汇合中不会列出从父类或者实现的接口中继承来的字段,但有可能列出本来 Java 代码之中不存在的字段。譬如在内部类中为了放弃对外部类的拜访性,会主动增加指向外部类实例的字段。
- 在 Java 语言中字段是无奈重载的,两个字段的数据类型,修饰符不论是否雷同,都必须应用不一样的名称,然而对于字节码来说,如果两个字段的描述符不统一,那字段名重名也是非法的。
fields_count(字段计数器)
fields_count 的值示意以后 Class 文件 fields 表的成员个数,用 2 个字节示意。
fields 表中每个成员都是一个 field_info 构造,用于示意该类或接口所申明的所有字段或者实例字段,不包含办法外部申明的变量,也不包含从父类或父接口继承的那些字段。
fields[](字段表)
- fields 表中的每个成员都必须是一个 field_info 构造的数据项,用于示意以后类或接口中某个字段的残缺形容。
-
一个字段的信息包含如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
- 作用域
- 实例变量还是类变量
- 是否 final
- 是否 volatile
- 是否序列化 transient 润饰
- 字段数据类型(根本数据类型,对象,数组)
- 字段名称
- 字段表构造
类型 | 名称 | 含意 | 数量 |
---|---|---|---|
u2 | access_flags | 拜访标记 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性汇合 | attributes_count |
字段表拜访标识
咱们晓得,一个字段能够被各种关键字润饰,比方作用域修饰符、static 修饰符、final 修饰符、volatile 修饰符等等。字段拜访标记有如下这些:
标记名称 | 标记值 | 含意 |
---|---|---|
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_TRANSTENT | 0x0080 | 字段是否为 transient |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器主动产生 |
ACC_ENUM | 0x4000 | 字段是否为 enum |
字段名索引
依据字段名索引的值,查问常量池中的指定索引项即可。
描述符索引
描述符的作用是用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值。依据描述符规定,根本数据类型及无返回值的 void 都是用大写符来示意,对象应用字符 L + 全限定名示意。
属性汇合
一个字段还可能领有一些属性,用于存储更多的额定信息。比方初始化值、一些正文信息等。属性个数寄存在 attribute_count 中,属性具体内容存在 attributes 数组中。
构造为:
ConstantValue_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
对于常量属性而言,attribute_length 的值恒为 2。
常量值索引所指向的 #8 其实就是对应 int 的值。
办法表汇合
methods:指向常量池索引汇合,它残缺形容了每个办法的签名。
- 在字节码文件中,
每一个 method_info 项都对应着一个类或者接口中的办法信息
。比方办法的拜访修饰符,办法的返回值类型,以及办法的参数信息等。 - 如果办法不是形象的或者不是 native 的,那么字节码就会体现进去。
- 一方面,methods 表只形容以后类或接口中申明的办法,不包含从父类或父接口继承的办法。另一方面,methods 表有可能会呈现由编译器主动增加的办法,最典型的就是编译器产生的办法信息,比方类或者接口初始化办法
<clinit>()
和实例初始化办法<init>()
。
methodds_count(办法计数器)
methods_count 的值示意以后 class 文件 methods 表的成员个数。应用 2 个字节来示意。
methods 表中每个成员都是一个 method_info 构造。
methods[](办法表)
- methods 表中的每个成员都必须是一个 method_info 构造,用于示意以后类或接口中某个办法的残缺形容。如果某个 method_info 构造的 access_flags 项既没有设置 ACC_NATIVE 标记和 ACC_ABSTRACT 标记,那么该构造中也应该蕴含实现这个办法所用到的 Java 虚拟机指令。
- method_info 构造能够示意类和接口中定义的所有办法,包含实例办法、类办法、实例初始化办法和类或接口初始化办法。
- 办法表的构造理论和字段表是统一的。如下:
类型 | 名称 | 含意 | 数量 |
---|---|---|---|
u2 | access_flags | 拜访标记 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性汇合 | attributes_count |
属性表汇合
办法表汇合之后的属性表汇合,指的是 class 文件所携带的辅助信息
,比方该 Class 文件的源文件的名称,以及任何带有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTIME 的注解。这类信息通常被用于 Java 虚拟机的验证和运行,以及 Java 程序的调试。
此外,字段表、办法表都能够有本人的属性表。用于形容某些场景专有的信息。
属性表汇合的限度没有那么严格,不再要求各个属性表具备严格的程序,并且只有不与已有的属性名反复,任何人实现的编译器都能够向属性表中写入本人定义的属性信息,但 Java 虚拟机运行时会疏忽掉它不意识的属性。
属性的通用格局
属性表的构造比拟灵便,各种不同的属性只有满足以下构造即可:
类型 | 名称 | 数量 | 含意 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u1 | info | attribute_length | 属性表 |
属性类型
属性名称 | 应用地位 | 含意 |
---|---|---|
Code | 办法表 | Java 代码编译成的字节码指令 |
ConstantValue | 字段表 | final 关键字定义的常量池 |
Deprecated | 类,办法,字段表 | 被申明为 deprecated 的办法和字段 |
Exceptions | 办法表 | 办法抛出的异样 |
EnclosingMethod | 类文件 | 仅当一个类为部分类或者匿名类是能力领有这个属性,这个属性用于标识这个类所在的外围办法 |
InnerClass | 类文件 | 外部类列表 |
LineNumberTable | Code 属性 | Java 源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code 属性 | 办法的局部变量形容 |
StackMapTable | Code 属性 | JDK1.6 中新增的属性,供新的类型查看测验器检查和解决指标办法的局部变量和操作数有所须要的类是否匹配 |
Signature | 类,办法表,字段表 | 用于反对泛型状况下的办法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | 用于存储额定的调试信息 |
Synthetic | 类,办法表,字段表 | 标记办法或字段为编译器主动生成的 |
LocalVariableTypeTable | 类 | 应用特色签名代替描述符,是为了引入泛型语法之后能形容泛型参数化类型而增加 |
RuntimeVisibleAnnotations | 类,办法表,字段表 | 为动静注解提供反对 |
RuntimeInvisibleAnnotations | 表,办法表,字段表 | 用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotation | 办法表 | 作用与 RuntimeVisibleAnnotations 属性相似,只不过作用对象为办法 |
RuntimeInvisibleParameterAnnotation | 办法表 | 作用与 RuntimeInvisibleAnnotations 属性相似,作用对象哪个为办法参数 |
AnnotationDefault | 办法表 | 用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | 用于保留 invokeddynamic 指令援用的疏导形式限定符 |
Code 属性
Code 属性就是寄存办法体外面的代码,像 接口或者形象办法
,没有具体的办法体,因而也就不会有 Code 属性了。
Code 属性表的构造:
类型 | 名称 | 数量 | 含意 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | max_stack | 1 | 操作数栈深度的最大值 |
u2 | max_locals | 1 | 局部变量表所需的存续空间 |
u4 | code_length | 1 | 字节码指令的长度 |
u1 | code | code_length | 存储字节码指令 |
u2 | exception_table_length | 1 | 异样表长度 |
exception_info | exception_table | exception_length | 异样表 |
u2 | attributes_count | 1 | 属性汇合计数器 |
attribute_info | attributes | attributes_count | 属性汇合 |
LineNumberTable 属性
- LineNumberTable 属性是可选变长属性,位于 Code 构造的属性表
- LineNumberTable 属性是用来形容 Java
源码行号
与字节码行号
之间的对应关系。 - start_pc,即字节码行号;line_number,即 Java 源代码的行号
- 在 Code 属性的属性表中,LineNumberTable 属性能够依照任意程序呈现,此外,多个 LineNumberTable 属性能够独特示意一个行号在源文件中示意的内容,即 LineNumberTable 属性不须要与源文件的行一一对应。
LineNumberTable 属性表构造
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];
}
类型 | 名称 | 数量 | 含意 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | line_number_table_length | 1 | 行号表长度 |
line_number_info | line_number_table | line_number_table_length | 行号表 |
SourceFile 属性
类型 | 名称 | 数量 | 含意 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | sourcefile_index | 1 | 源码文件索引 |
其长度总是固定的 8 个字节。
咱们看之前的 Excel 最初一位也能够看到对应的就是源文件名。