JVM中 Class 文件分析
每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和 64 位长度的数 据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。多字节数据项总是按照 Big-Endian1的顺 序进行存储。在Java SDK中,访问这种格式的数据可以使用java.io.DataInput、 java.io.DataOutput 等接口和 java.io.DataInputStream 和 java.io.DataOutputStream 等类来实现。
Class 文件的内容可用一组私有数据类型来表示,它们包括 u1,u2 和 u4,分别代 表了1、2和4个字节的无符号数。在 Java SDK 中这些类型的数据可以通过实现接口 java.io.DataInput 中的 readUnsignedByte、readUnsignedShort 和 readInt 方法进 行读取。
ClassFile 结构 每一个 Class 文件对应于一个如下所示的 ClassFile 结构体,其包含的属性如下表:
看到上表,很多人就会懵逼,这些都是啥啊,后面我会根据一个示例来讲解这些特性。
下面我们来看一个简单的 java 类,就是一个输出了一个 hello world。
- package com.hello.test;
- public class Log {
- public static void main(String[] args) {
- System.out.println("hello world!");
- }
- }
输入命令 javac Log.java 将其编译成 class 文件后,打开:
- cafe babe 0000 0034 001d 0a00 0600 0f09
- 0010 0011 0800 120a 0013 0014 0700 1507
- 0016 0100 063c 696e 6974 3e01 0003 2829
- 5601 0004 436f 6465 0100 0f4c 696e 654e
- 756d 6265 7254 6162 6c65 0100 046d 6169
- 6e01 0016 285b 4c6a 6176 612f 6c61 6e67
- 2f53 7472 696e 673b 2956 0100 0a53 6f75
- 7263 6546 696c 6501 0008 4c6f 672e 6a61
- 7661 0c00 0700 0807 0017 0c00 1800 1901
- 000c 6865 6c6c 6f20 776f 726c 6421 0700
- 1a0c 001b 001c 0100 1263 6f6d 2f68 656c
- 6c6f 2f74 6573 742f 4c6f 6701 0010 6a61
- 7661 2f6c 616e 672f 4f62 6a65 6374 0100
- 106a 6176 612f 6c61 6e67 2f53 7973 7465
- 6d01 0003 6f75 7401 0015 4c6a 6176 612f
- 696f 2f50 7269 6e74 5374 7265 616d 3b01
- 0013 6a61 7661 2f69 6f2f 5072 696e 7453
- 7472 6561 6d01 0007 7072 696e 746c 6e01
- 0015 284c 6a61 7661 2f6c 616e 672f 5374
- 7269 6e67 3b29 5600 2100 0500 0600 0000
- 0000 0200 0100 0700 0800 0100 0900 0000
- 1d00 0100 0100 0000 052a b700 01b1 0000
- 0001 000a 0000 0006 0001 0000 0003 0009
- 000b 000c 0001 0009 0000 0025 0002 0001
- 0000 0009 b200 0212 03b6 0004 b100 0000
- 0100 0a00 0000 0a00 0200 0000 0500 0800
- 0600 0100 0d00 0000 0200 0e
下面将根据 class 文件来分析每一个字节所代表的含义。
1. magic 魔数
Class 文件的第 1 - 4 个字节代表了该文件的魔数。
魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。
2. minor_version、major_version
Class 文件的第 5 - 6 个字节代表了 Class 文件的副版本号。
Class 文件的第 7 - 8 个字节代表了 Class 文件的主版本号。
副版本号和主版本号,minor_version 和 major_version 的值分别表示 Class 文件 的副、主版本。它们共同构成了 Class 文件的格式版本号。譬如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个 Class 文件的格式版本号就确定为 M.m。Class 文件格式版本号大小的顺序为:1.5 < 2.0 < 2.1。
一个 Java 虚拟机实例只能支持特定范围内的主版本号(Mi 至 Mj)和 0 至特定范围 内(0 至 m)的副版本号。假设一个 Class 文件的格式版本号为 V,仅当 Mi.0 ≤ v ≤ Mj.m 成立时,这个 Class 文件才可以被此 Java 虚拟机支持。不同版本的 Java 虚拟机实现 支持的版本号也不同,高版本号的 Java 虚拟机实现可以支持低版本号的 Class 文件。
下表列出了各个版本 JDK 的十六进制版本号信息:
上述 class 文件 0000 0034 对应的就是表格中的 JDK1.8。
3. 常量池集合
3.1 constant_pool_count 常量池计数器
紧跟版本信息之后的是常量池信息,其中前 2 个字节表示常量池计数器,其后的不定长数据则表示常量池的具体信息。
constant_pool_count 的值等于 constant_pool 表中的成员数加 1。 constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被认为是有效的,对于 long 和 double 类型有例外情况。在 Class 文件的常量池中,所有的 8 字节的常量都占两个表成员(项)的空间。如果一个 CONSTANT_Long_info 或 CONSTANT_Double_info 结构的项在常量池中的索引为 n,则常量池中下一个有效的项的索引为 n+2,此时常量池中索引为 n+1 的项有效但必须被认为不可用。
class 文件字节码对应的内容是:001d
,其值为 29,表示一共有 29 - 1 = 28 个常量。
3.2 constant_pool[]
紧跟着常量池计数器后面就是 28 个常量池了,因为每个常量都对应不同的类型,需要一个个具体分析。
常量池,constant_pool 是一种表结构,它包含 Class 文件结构及其子结构 中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相 同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为 "tag byte"。常量池的索引范围是 1 至 constant_pool_count − 1。
所有的常量池项都具有如下通用格式:
- cp_info {
- u1 tag;
- u1 info[];
- }
常量池中,每个 cp_info 项的格式必须相同,它们都以一个表示 cp_info 类型的单字节 “tag”项开头。后面 info[]项的内容 tag 由的类型所决定。tag 有效的类型和对应的取值在表 4.3 列出。每个 tag 项必须跟随 2 个或更多的字节,这些字节用于给定这个常量的信息,附加字节的信息格式由 tag 的值决定。
在 Java 虚拟机规范中一共有 14 种 cp_info
类型的表结构。
而上面这些 cp_info
表结构又有不同的数据结构,其对应的数据结构如下图所示。
接下来我们开始分析上述 Log.class 文件每个字节的含义,前面第一句话已经说了,紧跟着常量池计数器后面的就是常量池了。下面开始分析:
第 1 个常量
紧接着 001d 的后一个字节为 0A,为十进制数字 10,查表可知其为方法引用类型(CONSTANT_Methodref_info)的常量。在 cp_info 中结构如下所示:
查找的方式是先确定 tag 值,根据 tag 值判断当前属于哪一个常量。这里 tag 为 10,查表即可知是上图。
然后看其结构显示还有两个 U2 的index,说明后面 4 个字节都是属于第一个常量,其中第 2 - 3 个字节表示类信息,第 4 - 5 个字节表示名称及类描述符。
接下来我们取出这部分的数据:0a 0600 000f :
该常量项第 2 - 3 个字节,其值为 00 06,表示指向常量池第 6 个常量所表示的信息。根据后面我们分析的结果知道第 6 个常量是 java/lang/Object
。第 4 - 5 个字节,其值为 000f,表示指向常量池第 15 个常量所表示的信息,根据 javap 反编译出来的信息可知第 15 个常量是 <init>:()V
。将这两者组合起来就是:java/lang/Object.<init>:V
,即 Object 的 init 初始化方法。
下面是输入
- javap -v Log.class
反编译出来的结果:
- javap -v Log.class
- Classfile /Users/xxx/Desktop/Log.class
- Last modified 2020-1-8; size 427 bytes
- MD5 checksum 745be5a6df4d9554e783dbbcecaf9b6d
- Compiled from "Log.java"
- public class com.hello.test.Log
- minor version: 0
- major version: 52
- flags: ACC_PUBLIC, ACC_SUPER
- Constant pool:
- #1 = Methodref #6.#15 // java/lang/Object."<init>":()V
- #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
- #3 = String #18 // hello world!
- #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
- #5 = Class #21 // com/hello/test/Log
- #6 = Class #22 // java/lang/Object
- #7 = Utf8 <init>
- #8 = Utf8 ()V
- #9 = Utf8 Code
- #10 = Utf8 LineNumberTable
- #11 = Utf8 main
- #12 = Utf8 ([Ljava/lang/String;)V
- #13 = Utf8 SourceFile
- #14 = Utf8 Log.java
- #15 = NameAndType #7:#8 // "<init>":()V
- #16 = Class #23 // java/lang/System
- #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
- #18 = Utf8 hello world!
- #19 = Class #26 // java/io/PrintStream
- #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
- #21 = Utf8 com/hello/test/Log
- #22 = Utf8 java/lang/Object
- #23 = Utf8 java/lang/System
- #24 = Utf8 out
- #25 = Utf8 Ljava/io/PrintStream;
- #26 = Utf8 java/io/PrintStream
- #27 = Utf8 println
- #28 = Utf8 (Ljava/lang/String;)V
- {
- public com.hello.test.Log();
- descriptor: ()V
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #3 // String hello world!
- 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- LineNumberTable:
- line 5: 0
- line 6: 8
- }
- SourceFile: "Log.java"
其实从上面的结果也可以看出来,第一个常量对应的是第6,15个常量,组合起来的含义后面注释也写着了。
其他很多常量都是类似的,接下来我们看看字符串是怎么来得。
第 21 个常量
第 21 个常量,数据为
- 0100 1263 6f6d 2f68 656c 6c6f 2f74 6573 742f 4c6f 67
这里 tag 值是 01,对应的结构如下:
length 是 u2,对应着 0012,说明后面跟着 18 个字节:63 6f6d 2f68 656c 6c6f 2f74 6573 742f 4c6f 67;查 ASCII 表可得 63-c, 6f-o, 6d-m, 2f-/ ··· 4c-L,6f-o, 67-g,
组合起来就是:com/hello/test/Log 。
相信通过上面两个例子,大家就知道如何去分析常量池里面的索引了。但很多时候我们可以借助 JDK 提供的 javap 命令直接查看 Class 文件的常量池信息,但是手动分析能够让你更加了解结果为啥是这样的。其实 javap 出来的就是人家分析总结好的。
4. access_flags 访问标志
在常量池结束之后,紧接着的两个字节代表类或接口的访问标记(access_flags)。这里的数据为 00 21。
access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags 的取值范围和相应含义见下表:
第一列是标记名;
第二列是对应的值;
第三列是对应的说明。
带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员 编写的源代码生成的。
带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。
带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。
如果一个 Class 文件被设置了 ACC_INTERFACE 标志,那么同时也得设置 ACC_ABSTRACT 标志。同时它不能再设置ACC_FINAL、 ACC_SUPER 和 ACC_ENUM 标志。
注解类型必定带有 ACC_ANNOTATION 标记,如果设置了 ANNOTATION 标记, ACC_INTERFACE 也必须被同时设置。如果没有同时设置 ACC_INTERFACE 标记, 那么这个 Class 文件可以具有表 4.1 中的除 ACC_ANNOTATION 外的所有其它标记。 当然 ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标记除外。
ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪 一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记 是为了向后兼容旧编译器编译的 Class 文件而存在的,在 JDK1.0.2 版本以前的编 译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时, JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。
- 在表中没有使用的 access_flags 标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为 0, Java 虚拟机实现也会自动忽略它们。
5. 类索引、父类索引、接口索引
在访问标记后,则是类索引、父类索引、接口索引的数据,这里数据为:00 05 、00 06 、00 00。
类索引和父类索引都是一个 u2 类型的数据,而接口索引集合是一组 u2 类型的数据的集合,这个可以由前面 Class 文件的构成可以得到。Class 文件中由这三项数据来确定这个类的继承关系。
5.1 this_class 类索引
类索引,this_class 的值必须是对 constant_pool 表中项目的一个有效索引值。 constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。这里的类索引是 00 05 表示其指向了常量池中第 5 个常量,通过我们之前的分析,我们知道第 5 个常量其最终的信息是 Log 类。
5.2 super_class 父类索引
对于类来说,super_class 的值必须为 0 或者是对 constant_pool 表中 项目的一个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项 必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的 类的直接父类。当前类的直接父类,以及它所有间接父类的 access_flag 中都不能有 ACC_FINAL 标记。对于接口来说,它的 Class 文件的 super_class 项的值必须是 对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的 项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量。 如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是 java.lang.Object 类,只有它是唯一没有父类的类。这里的父类索引是 00 06 表示其指向了常量池中第 6 个常量,通过我们之前的分析,我们知道第 6 个常量其最终的信息是 Object 类。因为其并没有继承任何类,所以 Demo 类的父类就是默认的 Object 类。interfaces_count 接口计数器,
interfaces_count 的值表示当前类或接口的直接父接口数量。
5.3 interfaces[] 接口表
interfaces[] 数组中的每个成员的值必须是一个对 constant_pool 表中项 目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必 须为CONSTANT_Class_info类型常量,其中0 ≤ i < interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源 代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左 边的接口。
这里 Log 类的字节码文件中,因为并没有实现任何接口,所以紧跟着父类索引后的两个字节是0x0000,这表示该类没有实现任何接口。因此后面的接口索引表为空。
6. 字段表集合
字段表集合用于描述接口或者类中声明的变量,这里的数据为:00 00。
6.1 fields_count 字段计数器
fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。 fields[]数组中每一项都是一个 field_info 结构的数据项,它用于表示该类或接口声明的类字段或者实例字段。
6.2 fields[] 字段表
fields[]数组中的每个成员都必须是一个 fields_info 结构的数 据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口 声明的所有字段,但不包括从父类或父接口继承的部分。
- field_info {
- u2 access_flags;
- u2 name_index;
- u2 descriptor_index;
- u2 attributes_count;
- attribute_info attributes[attributes_count];
- }
7. 属性集合
7.1 attributes_count 属性计数器
attributes_count 的值表示当前 Class 文件 attributes 表的成员个 数。attributes 表中每一项都是一个 attribute_info 结构的数据项。
7.2 attributes[]属性表
属性表,attributes 表的每个项的值必须是 attribute_info 结构(§4.7)。
属性(Attributes)在 Class 文件格式中的 ClassFile 结构、field_info 结构,method_info 结构和 Code_attribute 结构都有使用,所有属性的通用格式如下:
对于任意属性,attribute_name_index 必须是对当前 Class 文件的常量池的有效 16 位 无符号索引。常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示当前属性的名字。attribute_length 项的值给出了跟随其后的字节的长度,这个长度不包括 attribute_name_index 和 attribute_name_index 项的 6 个字节。
有些属性因 Class 文件格式规范所需,已被预先定义好。这些属性在表 4.6 中列出,同时,被列出的信息还包括它们首次出现的Class文件版本和 Java SE 版本号。在本规范定义的环境 中,也就是已包含这些预定义属性的 Class 文件中,它们的属性名称被保留,不能再被属性表中其他的自定义属性所使用。 下表是 class 文件的属性:
8. 方法表集合
在字段表后的 2 个字节是一个方法计数器,表示类中总有有几个方法,在字段计数器后,才是具体的方法数据。这里数据为:00 02 。
8.1 methods_count 方法计数器
8.2 methods[] 方法表
methods[] 数组中的每个成员都必须是一个 method_info 结构的 数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构 的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志, 那么它所对应的方法体就应当可以被 Java 虚拟机直接从当前类加载,而不需要引用其它 类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、 实例初始化方法方法和类或接口初始化方法方法。methods[]数组 只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
- method_info {
- u2 access_flags;
- u2 name_index;
- u2 descriptor_index;
- u2 attributes_count;
- attribute_info attributes[attributes_count];
- }
Log 类的字节码文件中,方法计数器的值为 00 02,表示一共有 2 个方法。
第一个方法:
00 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0003
根据前面的结构格式可以知道,
方法计数器后 2 个字节表示方法访问标识,这里是 00 01,表示其实 ACC_PUBLIC 标识,对比上面的图表可知其表示 public 访问标识。
紧接着 2 个字节表示方法名称的索引,这里是 00 07 表示指向了常量池第 7 个常量,查阅可知其指向了<init>
。
紧接着的 2 个字节表示方法描述符索引项,这里是 00 08 表示指向了常量池第 8 个常量,查阅可知其指向了()V
。
紧接着 2 个字节表示属性表计数器,这里是 00 01 表示该方法的属性表一共有 1 个属性。属性表的表结构如下:
- attribute_info {
- u2 attribute_name_index;
- u4 attribute_length;
- u1 info[attribute_length];
- }
前两个字节是名字索引、接着 4 个字节是属性长度、接着是属性的值。这里前两个字节为 0009,指向了常量池第 9 个常量,查询可知其值为 Code,说明此属性是方法的字节码描述。
Code 属性的格式如下:
- Code_attribute {
- u2 attribute_name_index;
u4 attribute_length;- u2 max_stack;
- u2 max_locals;
- u4 code_length;
- u1 code[code_length];
- u2 exception_table_length; { u2 start_pc;
- u2 end_pc;
- u2 handler_pc;
- u2 catch_type;
- } exception_table[exception_table_length];
- u2 attributes_count;
- attribute_info attributes[attributes_count];
- }
根据 Code 属性对应表结构知道,前 2 个字节为 0009,即常量池第 9 个常量,查询知道是字符串常量 Code
。
接着 4 个字节表示属性长度,这里值为 0000 001d,即 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 那么长。
首先,前两个字节表示属性名称索引,这里数据为:00 0A。指向了第 10 个常量,查阅可知值为:LineNumberTable。LineNumberTable 属性是可选变长属性,位于 Code(§4.7.3)结构的属性表。它被调试 器用于确定源文件中行号表示的内容在 Java 虚拟机的 code[]数组中对应的部分。在 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];
- }
其前两个字节是属性名称索引,就是上面已经分析过的 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 03,即 line_number 表示 Java 源码行号为第 3 行。
从上面的反编译来看,上面的分析也是对的:
- {
- public com.hello.test.Log();
- descriptor: ()V
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #3 // String hello world!
- 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- LineNumberTable:
- line 5: 0
- line 6: 8
- }
第二个方法这里就不详细分析了,大家可以自己对着上面的反编译结果进行分析。
第二个方法开头是 0009,表示的是 ACC_PUBLIC 与 ACC_STATIC 合在一起的结果。
总结
到这里我们通过对 Log 类的解析,从而对 Java 类文件结构有了一个全面的认识。进一步还简单了解了 Java 虚拟机以及 Java 虚拟机规范。希望读完这篇文章,大家能对 Java 类文件结构有一个深入的认识。 最后用一张图来总结一下:
参考文章
JVM基础系列第5讲:字节码文件结构
JVM中 Class 文件分析的更多相关文章
- Omapl138中AIS文件分析(参照Using the OMAP-L138 Bootloader)(转)
Omapl138中AIS文件分析(参照Using the OMAP-L138 Bootloader) 转载链接:https://blog.csdn.net/qq_40788950/article/de ...
- JVM中class文件探索与解析(一)
一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何 ...
- JVM中class文件探索与解析
一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何 ...
- NS2中trace文件分析
ns中模拟出来的时间最终会以trace文件的形式告诉我们,虽然说一般都是用awk等工具分析trace文件,但是了解trace文件的格式也是必不可少的.下面就介绍一下无线网络模拟中trace文件的格式. ...
- JVM中启用逃逸分析
-XX:+DoEscapeAnalysis 逃逸分析优化JVM原理我们知道java对象是在堆里分配的,在调用栈中,只保存了对象的指针.当对象不再使用后,需要依靠GC来遍历引用树并回收内存,如果对象数量 ...
- Caffe源码中common文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中的一些重要头文件如caffe.hpp.blob.hpp等或者外部调用Caffe库使用时,一般都会in ...
- JVM中的逃逸分析
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术. 逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递 ...
- Maven项目中pom文件分析
pom英文全称: project object model 1.概述 pom.xml文件描述了maven项目的基本信息,比如groupId,artifactId,version等.也可以对maven项 ...
- Caffe源码中math_functions文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下math_functions文件. 1. include文件: ...
随机推荐
- Linux :ls 命令
常用命令: ls:列出当前路径下的文件和目录 ls -a:列出当前路径下的所有文件和目录(包括隐藏文件和目录) ls -l:以列表方式显示文件或目录的详细信息 ls -al:可以结合使用 ls xxx ...
- ipfs camp course c demo exercise 1
目录 aim: my bugs 解决ipfs 的 cros 问题的方法 result final code for c1 aim: 首先咱们把 broswer 和 自己的api 连接起来(要显示出来自 ...
- 【PAT甲级】1059 Prime Factors (25 分)
题意: 输入一个正整数N(范围为long int),输出它等于哪些质数的乘积. trick: 如果N为1,直接输出1即可,数据点3存在这样的数据. 如果N本身是一个质数,直接输出它等于自己即可,数据点 ...
- vue 和 jquery混合使用
有时候只要想到要用的 vue.js 的时候就会惯性的想起用vue-cli手脚架搭建一个项目,但是有时候的业务场景并不适合用vue-cli手脚架,这个时候使用vue+jquery混合使用,把他们的优点结 ...
- PS进程及杀掉进程!
1.程序和进程的关系(1)程序 保存在硬盘.光盘等介质中的可执行代码和数据 静态保存的代码 (2)进程 在 CPU 及内存中运行的程序代码 动态执行的代码 父.子进程:每一个进程可以创建一个或多个进程 ...
- [排错] SpringBoot 警告 Could not find acceptable representation
环境 Java 1.8 SpringBoot 2.1.9 Java 接口代码 @ResponseBody @RequestMapping(value = "cloud", meth ...
- JavaScript图形实例:线段构图
在“JavaScript图形实例:四瓣花型图案”和“JavaScript图形实例:蝴蝶结图案”中,我们绘制图形时,主要采用的方法是先根据给定的曲线参数方程计算出两点坐标,然后将两点用线段连接起来,线段 ...
- Swift3.0-基础知识
本文对Swift做一个从OC的角度的基础知识简单概要. Swift OC 说明 let.var const 在OC中不用const声明的常量,都认为是变量 Float.Double CGFloat ...
- 文件图标SVG
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink ...
- http://www.yyne.com/python使用-urllib-quote-进行-url-编码小技巧/
http://www.yyne.com/python使用-urllib-quote-进行-url-编码小技巧/