博主个人独立站点开通啦!欢迎点击访问:https://shuyi.tech
温馨提示:此篇文章长达两万字,图片50多张,内容非常多,建议收藏后再看。
前面我们说到 Java 虚拟机使用字节码实现了跨平台的愿景,无论什么系统,我们都可以使用 Java 虚拟机解释执行字节码文件。但其实字节码是有一套规范的,而规定字节码格式的就是《Java 虚拟机规范》。《Java 虚拟机规范》规定了 Java 虚拟机结构、Class 类文件结构、字节码指令等内容。其中类文件结构是有必要了解的一个内容。
字节码文件结构是一组以 8 位为最小基础的十六进制数据流,各数据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加任何分隔符。在字节码结构中,有两种最基本的数据类型来表示字节码文件格式,分别是:无符号数和表。
无符号数属于最基本的数据类型。它以 u1、u2、u4、u8 六七分别代表 1 个字节、2 个字节、4 个字节、8 个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成的字符串值。例如下表中第一行中的 u4 表示 Class 文件前 4 个字节表示该文件的魔数,第二行的 u2 表示该 Class 文件第 5-6 个字节表示该 JDK 的次版本号。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型。所有表都习惯性地以结尾。表用于描述有层次关系的复合结构的数据,例如下表第 5 行表示其实一个类型为 cp_info 的表(常量池),这里面存储了该类的所有常量。
而整个字节码文件本质上就是一张表,它由下面几个部分组成:
为了便于理解,我将一个完整的表划分为以下七个部分,这七个部分组成了一个完整的 Class 字节码文件:
- 魔数与Class文件版本
- 常量池
- 访问标志
- 类索引、父类索引、接口索引
- 字段表集合
- 方法表集合
- 属性表集合
在开始之前,我们先写一个最简单的入门 Hello World。接下来我们将以这个 Hello World 文件编译后的字节码文件为例子,来解析字节码文件内容。
接着在命令行运行命令编译这个类,这时会生成一个 Demo.class 文件。
接着我们用纯文本编辑器打开生成的 Demo.class 文件。
首先我们要清楚,字节码文件是使用十六进制进行编码的,而十六进制使用表示。接下来我们用上面「Hello World」的字节码文件为例子,一步步分析这七部分内容。
魔数与Class文件版本
Class 文件的第 1 - 4 个字节代表了该文件的魔数(Magic Number)。它唯一的作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件,其值固定是:0xCAFEBABE(咖啡宝贝)。如果一个 Class 文件的魔数不是 0xCAFEBABE,那么虚拟机将拒绝运行这个文件。
Class 文件的第 5 - 6 个字节代表了 Class 文件的次版本号(Minor Version),即编译该 Class 文件的 JDK 次版本号。
Class 文件的第 7 - 8 个字节代表了 Class 文件的主版本号(Major Version),即编译该 Class 文件的 JDK 主版本号。
高版本的 JDK 能向下兼容以前笨笨的 Class 文件,但不能运行新版本的 Class 文件。例如一个 Class 文件是使用 JDK 1.5 编译的,那么我们可以用 JDK 1.7 虚拟机运行它,但不能用 JDK 1.4 虚拟机运行它。下表列出了各个版本 JDK 的十六进制版本号信息:
我们看看之前的 Demo 文件的 Class 文件内容,其前 8 个字节分别是:。
对比上面表格中的数据,那么我们可以知道,这个 Class 文件是由 JDK1.8 编译的。
常量池
紧跟版本信息之后的是常量池信息,其中前 2 个字节表示常量池个数,其后的不定长数据则表示常量池的具体信息。
我们可以从上图知道,常量池的常量都是由这种表结构组成的,而且表结构不同其大小也不同。在 Java 虚拟机规范中一共有 14 种 类型的表结构。
而上面这些 表结构又有不同的数据结构,其对应的数据结构如下图所示。
表结构一共有三个字段,第一个字段表示这个表结构的标示值,有一个字节大小,对应我们上一个表格中的数字。第二、三个字段表示其表结构的描述,不同字段其意思不太一样。
看到这里可能有点犯模糊,这么些表格到底应该怎么用呢?没关系,我们举个例子就清楚了。
接下来我们继续看看 Hello World 字节码文件的内容。上一小节说到字节码文件的版本,那么接下来就是常量池的内容了。
Hello World 文件字节码对应的内容是:,其值为 29,表示一共有 29 - 1 = 28 个常量。
紧跟着常量池的就是 28 个常量了,因为每个常量都对应不同的类型,所以我们无法得知其具体大小,只能一个个分析。
第 1 个常量。紧接着 001d 的后一个字节为 0A,为十进制数字 10,查表可知其为方法引用类型(CONSTANT_Methodref_info)的常量。
再查 cp_info 对应的表结构知道,该常量项第 2 - 3 个字节表示类信息,第 4 - 5 个字节表示名称及类描述符。
接下来我们取出这部分的数据:。
该常量项第 2 - 3 个字节,其值为 00 06,表示指向常量池第 6 个常量所表示的信息。根据后面我们分析的结果知道第 6 个常量是 。第 4 - 5 个字节,其值为 java基础教程第5讲 000f,表示指向常量池第 15 个常量所表示的信息,根据 javap 反编译出来的信息可知第 10 个常量是 。将这两者组合起来就是:,即 Object 的 init 初始化方法。
大致就是按照上面的方式去分析每一个常量的值和意义,接下来我继续分析接下来的 27 个常量。
第 2 个常量,数据为 09 0010 0011。紧接着 000f 的后一个字节为 09,表示该常量为字段的符号引用(CONSTANT_Fieldref_info)。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示类信息,这里是 0010 表示指向常量池第 16 个常量所表示的信息,根据 javap 反编译我们知道其是 。该常量项的第 4 - 5 个字节表示名称及类描述符,这里值为 0011 表示指向常量池第 17 个常量所表示的信息,javap 反编译得知是 。结合起来就是:
第 3 个常量,数据为 08 00 12。紧接着 0011 的后一个字节为 08,表示该常量为字符串引用类型(CONSTANT_String_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示指向字符串字面量的索引,这里是 0012 表示指向常量池的第 18 个常量。javap 反编译得知其是一个字符串。
第 4 个常量,数据为 0A 0013 0014。紧接着 0012 的后一个字节为 0A,表示该常量为方法引用类型(CONSTANT_MethodHandle_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示类信息,这里是 0013 表示指向常量池第 19 个常量所表示的信息。该常量项的第 4 - 5 个字节表示名称及类描述符,这里值为 0014 表示指向常量池第 20 个常量所表示的信息。结果是:。
第 5 个常量,数据为 07 00 15。紧跟着 0014 后的是 07,表示是类信息类型常量,表结构如下。该表后紧跟着一个 2 个字节的索引,这里是 0015,其指向了常量池第 21 个常量,反编译得知其值为。
第 6 个常量,数据为 07 0016。07 表示其是类信息类型常量,其指向了常量池第 22 个常量。从后边的分析可以知道,第 22 个常量为字符串。
第 7 个常量,数据为 01 0006 3C 69 6E 69 74 3E。其中 01 表示其是字符串(CONSTANT_Utf8_info)的常量,0006 表示其字符串长度为 6 个字节。随后跟着的 3C 69 6E 69 74 3E 为字符串的值。在 Class 文件中,字符串是使用 ASCII 码进行编码的,我们将这些十六进制字符转换成对应的 ASCII 码之后,其值为:。
第 8 个常量,数据为 01 00 03 28 29 56。其中 01 表示其是字符串(CONSTANT_Utf8_info)的常量,0003 表示其字符串长度为 3 个字节。随后跟着的 28 29 56 为字符串的值。在 Class 文件中,字符串是使用 ASCII 码进行编码的,我们将这些十六进制字符转换成对应的 ASCII 码之后,其值为:。
第 9 个常量,数据为 01 00 04 43 6f 64 65。它是一个字符串常量,转换之后是:。
第 10 个常量,数据位 01 00 0f 4c 696e 654e 756d 6265 7254 6162 6c65 是一个字符串常量,转换之后是:。
第 11 个常量,数据为 01 00 04 6d 6169 6e。它是一个字符串常量,转换之后是:。
第 12 个常量,数据为 01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 是一个字符串常量,转换之后是:。
第 13 个常量,数据为 01 00 0a 53 6f75 7263 6546 696c 65。它是一个字符串常量,转换之后是:。
第 14 个常量,数据为 01 0009 4465 6d6f 2e6a 6176 61。它是一个字符串常量,转换之后是:。
第 15 个常量,数据为 0c 0007 0008。这里表示 tag 的值是 0C,表示该常量为方法引用类型(CONSTANT_NameAndType_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示字段或方法名的索引,这里是 0007 表示指向常量池第 7 个常量所表示的信息,即。该常量项的第 4 - 5 个字节表示字段或方法描述符的索引,这里值为 0008 表示指向常量池第 8 个常量所表示的信息,即。所以第 15 个常量表示的信息其实是:。
第 16 个常量,数据为 07 00 17。这里表示 tag 的值是 07,表示该常量为类信息类型(CONSTANT_Class_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示全限定名常量项的索引,这里是 0017 表示指向常量池第 23 个常量所表示的信息,即 。
第 17 个常量,数据为 0c 0018 0019。这里表示 tag 的值是 0C,表示该常量为方法引用类型(CONSTANT_NameAndType_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示字段或方法名的索引,这里是 0018 表示指向常量池第 24 个常量所表示的信息,即 。该常量项的第 4 - 5 个字节表示字段或方法描述符的索引,这里值为 0019 表示指向常量池第 25 个常量所表示的信息,即。所以第 17 个常量表示的信息其实是:。
第 18 个常量,数据为 01 00 0b 48 656c 6c6f 2057 6f72 6c64 。它是一个字符串常量,转换之后是:。
第 19 个常量,数据为 07 001a。这里表示 tag 的值是 07,表示该常量为类信息类型(CONSTANT_Class_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示全限定名常量项的索引,这里是 001A 表示指向常量池第 26 个常量所表示的信息,即。
第 20 个常量,数据为 0c 001b 001c 。这里表示 tag 的值是 0C,表示该常量为方法引用类型(CONSTANT_NameAndType_info)的常量。从上面的总表查阅知道,该常量项第 2 - 3 个字节表示字段或方法名的索引,这里是 001B 表示指向常量池第 27 个常量所表示的信息,即。该常量项的第 4 - 5 个字节表示字段或方法描述符的索引,这里值为 001C 表示指向常量池第 28 个常量所表示的信息,即。所以这里第 20 个常量的值为 。
第 21 个常量,数据为 01 00 04 44 656d 6f。是一个字符串常量,转换之后是:。
第 22 个常量,数据为 01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374。是一个字符串常量,转换之后是:。
第 23 个常量,数据为 01 00 10 6a 6176 612f 6c61 6e67 2f53 7973 7465 6d。是一个字符串常量,转换之后是:。
第 24 个常量,数据为 01 0003 6f75 74。是一个字符串常量,转换之后是:。
第 25 个常量,数据为 01 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b。是一个字符串常量,转换之后是:。
第 26 个常量,数据为 01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d。是一个字符串常量,转换之后是:。
第 27 个常量,数据为 01 0007 7072 696e 746c 6e。是一个字符串常量,转换之后是:。
第 28 个常量,数据为 01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 56。是一个字符串常量,转换之后是:。
到这里,我们常量池里 28 个常量已经全部解析完了。我们通过手动分析,了解了常量池的构成,但很多时候我们可以借助 JDK 提供的 javap 命令直接查看 Class 文件的常量池信息。
当我们运行时,控制台会打印出该 Class 文件的构成信息,其中就包括了常量池的信息。
将利用 javap 打印出的结果,与我们手动分析的结果对比一下,你会发现结果是一致的。
访问标志
在常量池结束之后,紧接着的两个字节代表类或接口的访问标记(access_flags)。这里的数据为 00 21。
这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口、是否定义为public类型、是否定义为abstract类型等。具体的标志位以及标志的含义见下表。
在这里这两个字节是 00 21,通过查看我们并没有发现有标志值是 00 21 的标志名称。这是因为这里的访问标志可能是由多个标志名称组成的,所以字节码文件中的标志值其实是多个值进行或运算的结果。
通过查阅上述表格,我们可以知道,00 21 由 00 01(第1行)和 00 20(第3行)进行或运算得来。也就是说该类的访问标志是 public 并且允许使用 invokespecial 字节码指令的新语义。
类索引、父类索引、接口索引
在访问标记后,则是类索引、父类索引、接口索引的数据,这里数据为:00 05 00 06 00 00。
类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class 文件中由这三项数据来确定这个类的继承关系。
类索引。类索引用于确定这个类的全限定名,它用一个 u2 类型的数据表示。这里的类索引是 00 05 表示其指向了常量池中第 5 个常量,通过我们之前的分析,我们知道第 5 个常量其最终的信息是 Demo 类。
父类索引。父类索引用于确定这个类的父类的全限定名,父类索引用一个u2类型的数据表示。这里的父类索引是 00 06 表示其指向了常量池中第 6 个常量,通过我们之前的分析,我们知道第 6 个常量其最终的信息是 Object 类。因为其并没有继承任何类,所以 Demo 类的父类就是默认的 Object 类。
接口索引。接口索引集合就用来描述哪个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身就是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。对于接口索引集合,入口第一项是 u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量,而在接口计数器后则紧跟着所有的接口信息。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
这里 Demo 类的字节码文件中,因为并没有实现任何接口,所以紧跟着父类索引后的两个字节是0x0000,这表示该类没有实现任何接口。因此后面的接口索引表为空。
字段表集合
字段表集合用于描述接口或者类中声明的变量,这里的数据为:00 00。
这里说的字段包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。在类接口集合后的2个字节是一个字段计数器,表示总有有几个属性字段。在字段计数器后,才是具体的属性数据。
字段表的每个字段用一个名为 field_info 的表来表示,field_info 表的数据结构如下所示:
因为我们并没有声明任何的类成员变量或类变量,所以在 Demo 的字节码文件中,字段计数器为 00 00,表示没有属性字段。
方法表集合
在字段表后的 2 个字节是一个方法计数器,表示类中总有有几个方法,在字段计数器后,才是具体的方法数据。这里数据为:00 02 。
方法表中的每个方法都用一个 method_info 表示,其数据结构如下:
Demo 类的字节码文件中,方法计数器的值为 00 02,表示一共有 2 个方法。
第 1 个方法,这里数据为:00 01 00 07 00 08 00 01 00 09 00 0000 1d 00 01 00 01 00 0000 05 2a b7 00 01 b1 0000 0001 000a 0000 0006 0001 0000 0001。方法计数器后 2 个字节表示方法访问标识,这里是 00 01,表示其实 ACC_PUBLIC 标识,对比上面的图表可知其表示 public 访问标识。紧接着 2 个字节表示方法名称的索引,这里是 00 07 表示指向了常量池第 7 个常量,查阅可知其指向了。紧接着的 2 个字节表示方法描述符索引项,这里是 00 08 表示指向了常量池第 8 个常量,查阅可知其指向了。
紧接着 2 个字节表示属性表计数器,这里是 00 01 表示该方法的属性表一共有 1 个属性。属性表的表结构如下:
前两个字节是名字索引、接着 4 个字节是属性长度、接着是属性的值。这里前两个字节为 0009,指向了常量池第9个常量,查询可知其值为Code,说明此属性是方法的字节码描述。 Code 属性的表结构如下:
根据 Code 属性对应表结构知道,前 2 个字节为 0009,即常量池第 9 个常量,查询知道是字符串常量。接着 4 个字节表示属性长度,这里值为 1D,即 29 的长度。下面我们继续分析 Code 属性的数据内容。
紧接着 2 个字节为 max_stack 属性。这里数据为 00 01,表示操作数栈深度的最大值。
紧接着 2 个字节为 max_locals属性。这里是数据为 00 01,表示局部变量表所需的存储空间为 1 个 Slot。在这里 max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。
接着 4 个字节为 code_length,表示生成字节码这里给的长度。这里数据为 00 00 00 05,表示生成字节码长度为 5 个字节。那么紧接着 5 个自己就是对应的数据,这里数据为 2a b7 00 01 b1,这一串数据其实就是字节码指令。通过查询字节码指令表,可知其对应的字节码指令:
- 读入2A,查表得0x2A对应的指令为aload_0,这个指令的含义是将第0个Slot中为reference类型的本地变量推送到操作数栈顶。
- 读入B7,查表得0xB7对应的指令为invokespecial,这条指令的作用是以栈顶的reference类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的方法符号引用。
- 读入00 01,这是invokespecial的参数,查常量池得0x0001对应的常量为实例构造器“”方法的符号引用。
- 读入B1,查表得0xB1对应的指令为return,含义是返回此方法,并且返回值为void。这条指令执行后,当前方法结束。
接着 2 个字节为异常表长度,这里数据为 00 00,表示没有异常表数据。那么接下来也就不会有异常表的值。
紧接着 2 个字节是属性表的长度,这里数据为 00 01,表示有一个属性。该属性长度为一个 attribute_info 那么长。attribute_info 属性表的表结构如下。
首先,前两个字节表示属性名称索引,这里数据为:00 0A。指向了第 10 个常量,查阅可知值为:LineNumberTable。LineNumberTable 表的表结构如下图所示。
其前两个字节是属性名称索引,就是上面已经分析过的 00 0A。
接着 4 个字节是属性长度,这里数据为 00 00 00 06,表示有 6 个字节的数据。接着 2 个字节是 LineNumberTable 的长度,这里数据是 00 01,表示长度为 1。接着跟着 1 个 line_number_info 类型的数据,下面是 line_number_info 表的结构,其包含了 start_pc 和 line_number 两个 u2 类型的数据项。前者是字节码行号,后者是 Java 源码行号。
那么接下来 2 个字节为 00 00,即 start_pc 表示的字节码行号为第 0 行。接着 00 01,即 line_number 表示 Java 源码行号为第 1 行。
到此,我们方法表集合的第一个方法分析结束。我们通过 javap 反编译查看,可以看到 Code 和 LineNumberTable 都是完全正确的。
接下来分析第 2 个方法。第二个方法的数据为:TODO。
前 2 个字节为方法访问标识,这里数据为 00 09,标识方法标识符为 public static void。
接着 2 个字节为方法名称索引项,这里数据为 00 0b,即常量池第 11 个常量,查询可知其值是。
接着 2 个字节为方法描述符索引项,这里数据为 00 0c,即常量池第 12 个常量,查询可知其值是。
接着 2 个常量标识属性表的数量,这里数据为 00 01,表示后面有 1 个类型为 表结构为 attribute_info 的属性信息。attribute_info 表的表结构如下。
即紧接着 2 个字符表示属性名的索引项,这里数据为 00 09,即对应常量池第 9 个常量,查询可知其值为:。Code 属性的表结构如下图所示。
Code 属性前 2 个字节表示其名字,这里分析过了,是。
接着 4 个字节表示属性的长度,这里数据是 00 00 00 25,表示长度为 37。
紧接着 2 个字节为 max_stack 属性。这里数据为 00 02,表示操作数栈深度的最大值为 2,其实是说有两个局部变量。
紧接着 2 个字节为 max_locals属性。这里是数据为 00 01,表示局部变量表所需的存储空间为 1 个 Slot。在这里 max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。
接着 4 个字节为 code_length,表示生成字节码这里给的长度。这里数据为 00 00 00 09,表示生成字节码长度为 9 个字节。那么紧接着 9 个自己就是对应的数据,这里数据为 b2 00 02 12 03 b6 00 04 b1,这一串数据其实就是字节码指令。通过查询字节码指令表,可知其对应的字节码指令。上面分析过一次了,这里就不再分析了。
接着 2 个字节为异常表长度,这里数据为 00 00,表示没有异常表数据。那么接下来也就不会有异常表的值。
紧接着 2 个字节是属性表的长度,这里数据为 00 01,表示有一个属性。该属性长度为一个 attribute_info 那么长。attribute_info 属性表的表结构如下。
首先,前两个字节表示属性名称索引,这里数据为:00 0A。指向了第 10 个常量,查阅可知值为:LineNumberTable。LineNumberTable 表的表结构如下图所示。
其前两个字节是属性名称索引,就是上面已经分析过的 00 0A。
接着 4 个字节是属性长度,这里数据为 00 00 00 0A,表示有 10 个字节的数据。接着 2 个字节是 LineNumberTable 的长度,这里数据是 00 02,表示长度为 2。接着跟着 2 个 line_number_info 类型的数据,下面是 line_number_info 表的结构,其包含了 start_pc 和 line_number 两个 u2 类型的数据项。前者是字节码行号,后者是 Java 源码行号。
第 1 个 line_number_info,即接下来 2 个字节为 00 00,即 start_pc 表示的字节码行号为第 0 行。接着 00 03,即 line_number 表示 Java 源码行号为第 3 行。
第 2 个 line_number_info,即接下来 2 个字节为 00 08,即 start_pc 表示的字节码行号为第 8 行。接着 00 04,即 line_number 表示 Java 源码行号为第 4 行。
这里的每个 line_number_info 占用 4 个字节,两个 line_number_info 一共 8 个字节。再加上表示 line_number_info 数量的 2 个字节,一共 10 个字节。刚好就与 attribute_length 的 00 00 00 0A 数据吻合。
到这里,第 2 个方法也分析结束了。同样我们通过命令反编译看看,会发现反编译的结果与我们分析的完全吻合,这说明我们的分析是正确的。
属性表集合
这里或许有人会迷惑,上面我们不是分析过属性表了么。其实上面分析的是方法中的属性,而这个是类中的属性。这个就像局部变量和类成员变量一样,是不同的。
紧接着我们剩下的数据为:00 0100 0d00 0000 0200 0e,这些就是属性表集合的数据了。
根据上面的表格我们知道,紧跟着的 2 个字节数据是属性表属性数量,这里数据为 00 01,表示有 1 个属性。后面紧跟着 1 个表结构为 attribute_info 的属性数据。attribute_info 表的结构如下图所示。
前两个字段为属性名称索引,这里数据为 00 0d,表示第 13 个常量池,查询可知这里的值是:。SourceFile 属性的表结构如下图所示。
SourceFile 表结构前两个字节我们已经分析过,数据为 00 0d,表示第 13 个常量池,指的是这个值。接着我们看后面 4 个字节,这里数据为 00 00 00 02,表示属性长度为 2 个字节。紧跟着的 2 个字节表示 SourceFile 的常量池索引,即该字节码文件的源文件名称,这里数据是 00 0e,即常量池的第 14 项,即。所以这个属性项标识了该字节码文件的源文件名称为 Demo.java。
我们通过 javap 反编译一下,可以发现与我们的分析完全一致。
到这里,我们就从头到尾将字节码文件的每个字节的数据分析完毕。通过这么一次分析,相信大家对于字节码文件的构成已经了然于胸了。这样的分析非常耗费时间,但是确实对字节码结构最好的一次学习。有时候最笨的方法,恰恰是最高效的方法。还没坚持下来的同学,要至少坚持独立分析一次,这样的收获是很大的。
但在实际使用或分析问题的时候,我们通常用 工具帮助我们完成这个过程,这样能提高效率。使用 工具很简单,只需要这样使用: 就可以将字节码文件全部分析出来。下面给出此次 Demo.class 文件的反编译完整截图。
总结
到这里我们通过对 Hello World 的解析,从而对 Java 类文件结构有了一个全面的认识。进一步还简单了解了 Java 虚拟机以及 Java 虚拟机规范。希望读完这篇文章,大家能对 Java 类文件结构有一个深入的认识。 最后用一张图来总结一下:
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/java-jiao-cheng/20073.html