1.Class文件基本结构概述
- Class文件是一组以8位字节为基础单位的二进制流,当遇到需要8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。
- Class文件由无符号数和表构成。
- 无符号数:以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值、按照UTF-8编码构成的字符串值。
- 表:由多个无符号数或其他表作为数据项构成的复杂数据类型,所有表都习惯性地以“_info”结尾。
- Class文件格式如下:
2.Class文件示例
- 源代码示例
//接口
package com.example.jacoco;
public interface Animal {
public void eat();
}
//实现类
package com.example.jacoco;
public class Cat implements Animal {
public static String type = "cat";
public String name;
@Override
public void eat(){
System.out.println("-----------eat-----------");
}
public String getName() {
return name;
}
}
- class文件示例(16进制的class文件)
cafe babe 0000 0034 002d 0700 0201 0016
636f 6d2f 6578 616d 706c 652f 6a61 636f
636f 2f43 6174 0700 0401 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0700 0601
0019 636f 6d2f 6578 616d 706c 652f 6a61
636f 636f 2f41 6e69 6d61 6c01 0004 7479
7065 0100 124c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b01 0004 6e61 6d65 0100
083c 636c 696e 6974 3e01 0003 2829 5601
0004 436f 6465 0800 0e01 0003 6361 7409
0001 0010 0c00 0700 0801 000f 4c69 6e65
4e75 6d62 6572 5461 626c 6501 0012 4c6f
6361 6c56 6172 6961 626c 6554 6162 6c65
0100 063c 696e 6974 3e0a 0003 0015 0c00
1300 0b01 0004 7468 6973 0100 184c 636f
6d2f 6578 616d 706c 652f 6a61 636f 636f
2f43 6174 3b01 0003 6561 7409 001a 001c
0700 1b01 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0c00 1d00 1e01 0003 6f75
7401 0015 4c6a 6176 612f 696f 2f50 7269
6e74 5374 7265 616d 3b08 0020 0100 192d
2d2d 2d2d 2d2d 2d2d 2d2d 6561 742d 2d2d
2d2d 2d2d 2d2d 2d2d 0a00 2200 2407 0023
0100 136a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 0c00 2500 2601 0007 7072
696e 746c 6e01 0015 284c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5601 0007
6765 744e 616d 6501 0014 2829 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0900
0100 2a0c 0009 0008 0100 0a53 6f75 7263
6546 696c 6501 0008 4361 742e 6a61 7661
0021 0001 0003 0001 0005 0002 0009 0007
0008 0000 0001 0009 0008 0000 0004 0008
000a 000b 0001 000c 0000 0026 0001 0000
0000 0006 120d b300 0fb1 0000 0002 0011
0000 0006 0001 0000 0004 0012 0000 0002
0000 0001 0013 000b 0001 000c 0000 002f
0001 0001 0000 0005 2ab7 0014 b100 0000
0200 1100 0000 0600 0100 0000 0300 1200
0000 0c00 0100 0000 0500 1600 1700 0000
0100 1800 0b00 0100 0c00 0000 3700 0200
0100 0000 09b2 0019 121f b600 21b1 0000
0002 0011 0000 000a 0002 0000 0009 0008
000b 0012 0000 000c 0001 0000 0009 0016
0017 0000 0001 0027 0028 0001 000c 0000
002f 0001 0001 0000 0005 2ab4 0029 b000
0000 0200 1100 0000 0600 0100 0000 0e00
1200 0000 0c00 0100 0000 0500 1600 1700
0000 0100 2b00 0000 0200 2c
- javap -v Cat.class输出的class的文件结构
Last modified 2019-4-7; size 779 bytes
MD5 checksum e33bee3260446d95b04b553983a2f3a1
Compiled from "Cat.java"
public class com.example.jacoco.Cat implements com.example.jacoco.Animal
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/example/jacoco/Cat
#2 = Utf8 com/example/jacoco/Cat
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // com/example/jacoco/Animal
#6 = Utf8 com/example/jacoco/Animal
#7 = Utf8 type
#8 = Utf8 Ljava/lang/String;
#9 = Utf8 name
#10 = Utf8 <clinit>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = String #14 // cat
#14 = Utf8 cat
#15 = Fieldref #1.#16 // com/example/jacoco/Cat.type:Ljava/lang/String;
#16 = NameAndType #7:#8 // type:Ljava/lang/String;
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 <init>
#20 = Methodref #3.#21 // java/lang/Object."<init>":()V
#21 = NameAndType #19:#11 // "<init>":()V
#22 = Utf8 this
#23 = Utf8 Lcom/example/jacoco/Cat;
#24 = Utf8 eat
#25 = Fieldref #26.#28 // java/lang/System.out:Ljava/io/PrintStream;
#26 = Class #27 // java/lang/System
#27 = Utf8 java/lang/System
#28 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = String #32 // -----------eat-----------
#32 = Utf8 -----------eat-----------
#33 = Methodref #34.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V
#34 = Class #35 // java/io/PrintStream
#35 = Utf8 java/io/PrintStream
#36 = NameAndType #37:#38 // println:(Ljava/lang/String;)V
#37 = Utf8 println
#38 = Utf8 (Ljava/lang/String;)V
#39 = Utf8 getName
#40 = Utf8 ()Ljava/lang/String;
#41 = Fieldref #1.#42 // com/example/jacoco/Cat.name:Ljava/lang/String;
#42 = NameAndType #9:#8 // name:Ljava/lang/String;
#43 = Utf8 SourceFile
#44 = Utf8 Cat.java
{
public static java.lang.String type;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #13 // String cat
2: putstatic #15 // Field type:Ljava/lang/String;
5: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
public com.example.jacoco.Cat();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #20 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/jacoco/Cat;
public void eat();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #31 // String -----------eat-----------
5: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 11: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/example/jacoco/Cat;
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #41 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 14: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/jacoco/Cat;
}
SourceFile: "Cat.java"
3.魔数
- 每个Class文件的头4个字节称为魔数(Magic Number)
- 唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。
- Class文件魔数的值为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。
4.版本号
紧接着魔数的4个字节是Class文件版本号,版本号又分为:
- 次版本号(minor_version): 前2字节用于表示次版本号
- 主版本号(major_version): 后2字节用于表示主版本号。
这个的版本号是随着jdk版本的不同而表示不同的版本范围的。Java的版本号是从45开始的。如果Class文件的版本号超过虚拟机版本,将被拒绝执行。
0X0034(对应十进制的50):JDK1.8
0X0033(对应十进制的50):JDK1.7
0X0032(对应十进制的50):JDK1.6
0X0031(对应十进制的49):JDK1.5
0X0030(对应十进制的48):JDK1.4
0X002F(对应十进制的47):JDK1.3
0X002E(对应十进制的46):JDK1.2
ps:0X表示16进制
5.常量池
Java代码在进行Java编译的时候,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法和字段的最终内存布局信息,因此这些字段和方法的符号引用不经过转换的话是无法被虚拟机使用的。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析并翻译到具体的内存地址之中。
紧接着魔数与版本号之后的是常量池入口.常量池简单理解为class文件的资源从库
- 是Class文件结构中与其它项目关联最多的数据类型
- 是占用Class文件空间最大的数据项目之一
- 是在文件中第一个出现的表类型数据项目
5.1.constant_pool_count:
- 常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型(占2字节)的数据,代表常量池容量计数值。
- 本例为0x0016,转化为十进制为22,即说明常量池中有21个常量(只有常量池的计数是从1开始的,其它集合类型均从0开始),索引值为1~21。
- 第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示
-
常量池之中主要存放两大类常量:
- 字面量: 比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
-
符号引用: 属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
5.2.constant_pool:
- 表类型数据集合,即常量池中每一项常量都是一个表
- 共有14种(JDK1.7前只有11种)结构各不相同的表结构数据。这14种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构如下表所示:
6.access flag
在常量池结束之后,紧接着的两个字节代表訪问标志。用于识别一些类或者接口层次的访问信息。例如以下表所看到的:
依据上面的表格,Cat.java的访问标志0x0021= 0x0001 | 0x0020 =ACC_PUBLIC | ACC_SUPER
7.类索引、父类索引、接口索引集合
- 这三项数据主要用于确定这个类的继承关系,其中类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引(interface)集合是一组u2类型的数据,(多实现单继承)。
- 类索引(this_class),用于确定这个类的全限定名,占2字节
- 父类索引(super_class),用于确定这个类父类的全限定名(Java语言不允许多重继承,故父类索引只有一个。除了java.lang.Object类之外所有类都有父类,故除了java.lang.Object类之外,所有类该字段值都不为0),占2字节
- 接口索引计数器(interfaces_count),占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节,
- 接口索引集合(interfaces),一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中
- this_class、super_class与interfaces按顺序排列在访问标志之后,它们中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量,通过这个常量中保存的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
如示例中的
0001 -- this class index
0003 -- super class index
0001 -- interface count
0005 -- interfaces index
8.字段表集合
- fields_count:字段表计数器,即字段表集合中的字段表数据个数,占2字节。本测试类其值为0x0001,即只有一个字段表数据,也就是测试类中只包含一个变量(不算方法内部变量)
- fields:字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量
- field表的结构:在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示。字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息。字段表的结构如下图
- 字段表集合中不会列出从父类或父接口中继承的字段,但是可能列出原本Java代码之中不存在的字段,如:内部类为了保持对外部类的访问性,自动添加指向外部类实例的字段。Java语言中字段是不能重载的,2个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的。
8.1.字段的access flag:
字段修饰符放在access_flags中,占2字节。
8.2.字段的name index:
name index表示的16进制的值为指向常量池的某一项常量,代表的字段的名称
8.3.字段的descriptor_index:
代表着字段的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。
8.4.attributes_count和attribute表
在descriptor_index之后会跟随0-n项额外信息,详见属性表
如示例中的
0002 -- feild count
0009 -- access flag:public static
0007 -- name index:type
0008 -- descript index: Ljava/lang/String;
0000 -- attribute_info:0
0001 -- access flag:public
0009 -- name index:name
0008 -- descript index: Ljava/lang/String;
0000 -- attribute_info:0
9.方法表集合
- methods_count:方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法
- methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样。
- method access flag:
0004 -- method count
0008 -- access flag
000a -- name index
000b -- descript index
0001 -- attributes count
10.属性表集合
在Class文件、字段表和方法表都可以携带自己的属性信息,这个信息用属性表进行描述,用于描述某些场景专有的信息。
与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性(预定义属性已经增加到21项)
10.1.常见的属性:
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_utf8_info类型的常量类表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性区说明属性值所占用的位数即可.
- 属性表定义的结构:对于每个属性,它的名称需要从常量池中引用一个CONSTANT_utf8_info类型的常量类表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性区说明属性值所占用的位数即可.
10.2.CODE属性:
Java程序方法体中的代码经过Javac编译处理后,最终变为字节码指令存储在Code属性中.Code属性出现在方法表的属性集合中,但是并非所有的方法表都有这个属性.例如接口或类中的方法就不存在Code属性了.在字节码指令之后的是方法的是方法的显式异常处理表集合,异常表对于Code属性来说并不是必须参在的.
- code属性的结构:
- attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,他代表了该属性的属性名称,
- attribute_length指示了属性值的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减少6个字节。
- max_stack代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值分配栈帧(Stack Frame)中的操作帧深度。
- max_locals代表了局部变量表所需的存储空间。在这里,max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用1个Slot,而double和long这两种64位的数据类型则需要两个Slot来存放。方法参数(包括实例方法中的隐藏参数“this”)、显式异常处理器的参数(Exception Handler Parameter,就是try-catch语句中catch块所定义的异常)、方法体中定义的局部变量都需要使用局部变量表来存放。另外,并不是在方法中用到了多少个局部变量,就把这些局部变量所占Slot之和作为max_locals的值,原因是局部变量表中的Slot可以重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占的Slot可以被其他局部变量所使用,Javac编译器会根据变量的作用域来分配Slot给各个变量使用,然后计算出max_locals的大小。
- code_length和code用来存储java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。既然叫字节码指令,那么每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及参数应当如何理解。我们知道一个u1数据类型的取值范围为0x00~0xFF,对应十进制的0~255,也就是一共可以表达256条指令,目前,Java虚拟机规范已经定义了其中约200条编码值对应的指令含义。关于code_length,有一件值得注意的事情,虽然他是一个u4类型的长度值,理论上最大值可以达到2的32次方减1,但是虚拟机规范中明确限制了一个方法不允许超过65535条字节码指令,即他实际只使用了u2的长度,如果超过这个限制,Javac编译器也会拒绝编译。一般来讲,编写Java代码时只要不是刻意去编写一个超长的方法来为难编译器,是不太可能超过这个最大值的限制。但是,某些特殊情况,例如在编译一个很复杂的JSP文件时,某些JSP编译器会把JSP内容和页面输出的信息归并于一个方法之中,就可能因为方法生成字节码超长的原因而导致编译失败。
- exception_table_length和exception_info:exception_table_length是u2类型,2个字节。异常表对Code属性来说不是必须存在, 在本实例中为0x0000,说明不存在异常表。
- attributes_count和attributes:为Code表中包含的其他属性,如: LineNumberTable属性,用于描述java源码行号和字节码行号之间的对应关系。 LocalVariableTable属性,用户描述栈中局部变量表中的变量和java源码中定义的变量的关系。 异常表的格式如下表所示,他包含4个字段,这些字段的含义为:如果当字节码在第start_pc行到end_pc行之间(不含第end_pc行)出现了类型为catch_type或者其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向到handler_pc处进行处理。
10.3.Exceptions属性:
这里的Exceptions属性是在方法表与Code属性平级的一项属性。Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Exceptions),也就是说方法描述时在throws关键字啊后面列举的异常。他的结构见下表。Exceptions属性中的number_of_exceptions项表示方法可能抛出number_of_exceptions种受查异常,每一种受查异常使用一个exception_index_table项表示,exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型。
10.4.LineNumberTable属性:
LineNumberTable是Code属性中的一个子属性,用来描述java源文件行号与字节码文件偏移量之间的对应关系。当程序运行抛出异常时,异常堆栈中显示出错的行号就是根据这个对应关系来显示的,它的结构如下:
- 其中line_number_info结构如下:
10.5.LocalVariableTable属性:
也是Code属性中的一个子属性,用来描述栈帧的局部变量表中变量与java源码中变量的对应关系,其结构如下:
-
其中local_variable_info结构如下:
- start_pc和length属性分别代表了这个局部变量的生命周期开始地字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。
- name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。
- index是这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是 64位类型时(double和long),他占用的Slot为index和index+1两个。
10.6 其他属性:
参考https://blog.csdn.net/qq_35892775/article/details/85860479
10.7.属性表示例分析
如示例中的
0004 -- method count
0008 -- access flag
000a -- name index:<clinit>
000b -- descript index
0001 -- attributes count
000c -- attribute_name_index : 对应Code属性
0000 0026 -- attribute_length:38个字节
0001 -- max_stack
0000 -- max_locals
0000 0006 -- code_length
120d b300 0fb1 -- code:指令
0000 -- exception_table_length
0002 -- attributes_count
0011 -- attribute_name_index:LineNumberTable
0000 0006 -- line_number_table_length
0001 -- line_number_table
0000 -- start_pc:字节码偏移量
0004 -- line_number:java源文件行号
0012 -- attribute_name_index:LocalVariableTable
0000 0002 -- attribute_length
0000 -- local_variable_table_length
0001 -- access flag
0013 -- name index:<init>
000b -- descript index
0001 -- attributes count
000c -- attribute_name_index : 对应Code属性
0000 002f -- attribute_length:47个字节
0001 -- max_stack
0001 -- max_locals
0000 0005 -- code_length
2ab7 0014 b1 -- code:指令
0000 -- exception_table_length
0002 -- attributes_count
0011 -- attribute_name_index:LineNumberTable
0000 0006 -- line_number_table_length
0001 -- line_number_table
0000 -- start_pc:字节码偏移量
0003 -- line_number:java源文件行号
0012 -- attribute_name_index:LocalVariableTable
0000 000c -- attribute_length
0001 -- local_variable_table_length
0000 -- start_pc
0005 -- length
0016 -- name_index
0017 -- descriptor_index
0000 -- index
00
0100 1800 0b00 0100 0c00 0000 3700 0200
0100 0000 09b2 0019 121f b600 21b1 0000
0002 0011 0000 000a 0002 0000 0009 0008
000b 0012 0000 000c 0001 0000 0009 0016
0017 0000 0001 0027 0028 0001 000c 0000
002f 0001 0001 0000 0005 2ab4 0029 b000
0000 0200 1100 0000 0600 0100 0000 0e00
1200 0000 0c00 0100 0000 0500 1600 1700
0000 0100 2b00 0000 0200 2c
Java字节码指令大全
参考:https://blog.csdn.net/github_35983163/article/details/52945845
参考:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html