-
-
- class文件结构
- Class文件设计理念和意义
- 1. 魔数magic
- minor_version、major_version
- 2. constant_pool_count
- 3. constant_pool[]常量池
- 4. access_flags:访问标志
- 5. this_class:类索引
- 6. super_class:父类索引
- 7. interfaces_count:接口计数器
- 8. interfaces[]:接口表
- 9. fields_count:字段计数器
- 10. fields[]:字段表
- 11. methods_count:方法计数器
- 12. methods[]:方法表
- 13. attributes_count:属性计数器
- 14. attributes[]:属性表
- 字节码指令
- 一个简单的demo分析
-
class文件结构
Class文件存储的内容称为字节码(ByteCode),包含了JVM指令集和符号表以及若干其他辅助信息。
class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何分隔符,整个Class文件中存储的内容几乎全部是程序运行的必要的数据,没有空隙存在。
当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
Class文件中有两种数据类型,分别是无符号数和表。
无符号数
无符号数属于基本的数据类型,以u1、u2、u4、u8来表示一个字节、两个字节…的无符号数;无符号数用来描述数字、索引引用、数量值或UTF-8编码构成的字符串值。
表
表是由多个无符号数或其他表作为数据项构成的复合数据类型,一般以”_info”结尾,用来描述class文件的数据结构。
特点:节省存储空间,提高处理性能
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- 魔数
- Class文件版本
- 常量池
- 访问标志
- 类索引,父类索引,接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
u2表示无符号数2个字节
u4表示无符号数4个字节
Class文件设计理念和意义
1. 魔数magic
魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。
证明magic作用
创建一个class文件 magic.class ,内容是magic test,直接运行java magic
操作:
84407@FantJ MINGW64 ~/Desktop
$ java magictest
▒▒▒▒: ▒▒▒▒▒▒▒▒ magictest ʱ▒▒▒▒ LinkageError
java.lang.ClassFormatError: Incompatible magic value 1886741100 in class file magictest
报错意思是:magic矛盾,然后给了个magic value的十进制数,那么可以识别的magic十进制应该是多少呢。
应该是3405691582
那么,然后我用javac编译的正常java文件生成class文件,用binary viewer 查看:
minor_version、major_version
魔数往后后面四位:表示字节码版本,分别表示Class文件的副、主版本。当今用的最广的几个版本:
jdk1.8:52
jdk1.7:51
jdk1.6:50
对应版本号是52,是jdk1.8
版本向下兼容
2. constant_pool_count
常量池计数器,值等于constant_pool表中的成员数加1,占用两个字节
3. constant_pool[]常量池
Java虚拟机指令执行时依赖常量池(constant_pool)表中的符号信息。
所有的常量池项都具有如下通用格式:
cp_info {
u1 tag;
u1 info[];
}
info[]项的内容tag由的类型所决定。tag有效的类型和对应的取值在下表列出
常量类型 | 值 |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
3.1 CONSTANT_Class_info结构
表示类或接口
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
name_index
必须是对常量池的一个有效索引
3.2 CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info结构
字段:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
方法:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
接口方法:
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
class_index
必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
CONSTANT_Methodref_info
结构的class_index
项的类型必须是类(不能是接口)。CONSTANT_InterfaceMethodref_info
结构的class_index
项的类型必须是接口(不能是类)。CONSTANT_Fieldref_info
结构的class_index
项的类型既可以是类也可以是接口。
name_and_type_index
必须是对常量池的有效索引,表示当前字段或方法的名字和描述符。
在一个CONSTANT_Fieldref_info
结构中,给定的描述符必须是字段
描述符。而CONSTANT_Methodref_info
和CONSTANT_InterfaceMethodref_info
中给定的描述符必须是方法
描述符。
3.3 CONSTANT_String_info结构
用来表示String的结构
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
string_index
必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,表示一组Unicode码点序列,这组Unicode码点序列最终会被初始化为一个String对象。
3.4CONSTANT_Integer_info和CONSTANT_Float_info结构
表示4字节(int和float)的数值常量:
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
3.5CONSTANT_Long_info和CONSTANT_Double_info结构
表示8字节(long和double)的数值常量
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
3.6 CONSTANT_NameAndType_info结构
表示字段或方法,但是和前面介绍的3个结构不同,CONSTANT_NameAndType_info结构没有标识出它所属的类或接口
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
name_index
项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
descriptor_index
项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构表示一个有效的字段描述符或方法描述符。
3.7 CONSTANT_Utf8_info结构
用于表示字符串常量的值
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info结构中的内容是以length
属性确定长度的
3.8 CONSTANT_MethodHandle_info结构
表示方法句柄
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
reference_kind
项的值必须在1至9之间(包括1和9),它决定了方法句柄的类型。
1. 如果reference_kind
项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic)
,那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段创建的方法句柄。
2. 如果reference_kind
项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial)
,那么常量池在reference_index
索引处的项必须是CONSTANT_Methodref_info
结构,表示由类的方法或构造函数创建的方法句柄。
3. 如果reference_kind
项的值是9(REF_invokeInterface)
,那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法创建的方法句柄。
4. 如果reference_kind
项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface)
,那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。
5. 如果reference_kind
项的值是8(REF_newInvokeSpecial)
,那么方法句柄对应的方法必须为实例初始化()方法。
3.9 CONSTANT_MethodType_info结构
表示方法类型
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
3.10 CONSTANT_InvokeDynamic_info结构
表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
bootstrap_method_attr_index
项的值必须是对当前Class文件中引导方法表的bootstrap_methods[]
数组的有效索引。
name_and_type_index
项的值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info
结构,表示方法名和方法描述符。
4. access_flags:访问标志
访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表。
标记名 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 可以被包的类外访问。 |
ACC_FINAL | 0x0010 | 不允许有子类。 |
ACC_SUPER | 0x0020 | 当用到invokespecial指令时,需要特殊处理的父类方法。 |
ACC_INTERFACE | 0x0200 | 标识定义的是接口而不是类。 |
ACC_ABSTRACT | 0x0400 | 不能被实例化。 |
ACC_SYNTHETIC | 0x1000 | 标识并非Java源码生成的代码。 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 标识枚举类型 |
5. this_class:类索引
this_class的值必须是对constant_pool表中项目的一个有效索引值。
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
6. super_class:父类索引
表示这个Class文件所定义的类的直接父类,如果Class文件的super_class的值为0,那这个Class文件只可能是定义的是java.lang.Object类,只有它是唯一没有父类的类
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
7. interfaces_count:接口计数器
表示有这个类有几个接口。
8. interfaces[]:接口表
成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
表示当前类或接口的直接父接口数量
9. fields_count:字段计数器
表示当前Class文件fields[]数组的成员个数
10. fields[]:字段表
每个成员都必须是一个fields_info结构的数据项,描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的部分。
用于表示当前类或接口中某个字段的完整描述
field_info {
u2 access_flags;
u2 name_index; //对常量池的一个有效索引
u2 descriptor_index; //对常量池的一个有效索引
u2 attributes_count; //当前字段的附加属性的数量
attribute_info attributes[attributes_count];
}
access_flags
项的值是用于定义字段被访问权限和基础属性的掩码标志。access_flags的取值范围和相应含义见下表所示:
标记名 | 值 | 说明 |
---|---|---|
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_TRANSIENT | 0x0080 | transient,表示字段不会被序列化。 |
ACC_SYNTHETIC | 0x1000 | 表示字段由编译器自动产生。 |
ACC_ENUM | 0x4000 | enum,表示字段为枚举类型。 |
attributes
表的每一个成员的值必须是attribute结构
,一个字段可以有任意个关联属性。
11. methods_count:方法计数器
methods_count的值表示当前Class文件methods[]数组的成员个数,Methods[]数组中每一项都是一个method_info结构的数据项。
12. methods[]:方法表
method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法方法和类或接口初始化方法方法。methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags
项的值是用于定义当前方法的访问权限和基本属性的掩码标志,access_flags的取值范围和相应含义见下表所示。
标记名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | public,方法可以从包外访问 |
ACC_PRIVATE | 0x0002 | private,方法只能本类中访问 |
ACC_PROTECTED | 0x0004 | protected,方法在自身和子类可以访问 |
ACC_STATIC | 0x0008 | static,静态方法 |
ACC_FINAL | 0x0010 | final,方法不能被重写(覆盖) |
ACC_SYNCHRONIZED | 0x0020 | synchronized,方法由管程同步 |
ACC_BRIDGE | 0x0040 | bridge,方法由编译器产生 |
ACC_VARARGS | 0x0080 | 表示方法带有变长参数 |
ACC_NATIVE | 0x0100 | native,方法引用非java语言的本地方法 |
ACC_ABSTRACT | 0x0400 | abstract,方法没有具体实现 |
ACC_STRICT | 0x0800 | strictfp,方法使用FP-strict浮点格式 |
ACC_SYNTHETIC | 0x1000 | 方法在源文件中不出现,由编译器产生 |
name_index
和 descriptor_index
两属性是对常量池的一个有效索引 attributes_count
的项的值表示这个方法的附加属性的数量。 attributes
表的每一个成员的值必须是attribute
结构,一个方法可以有任意个与之相关的属性。
13. attributes_count:属性计数器
attributes表中每一项都是一个attribute_info结构的数据项。
attributes_count的值表示当前Class文件attributes表的成员个数。
14. attributes[]:属性表
attributes表的每个项的值必须是attribute_info结构,在Class文件格式中的ClassFile结构、field_info结构,method_info结构和Code_attribute结构都有使用,所有属性的通用格式如下:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
attribute_name_index
必须是对当前Class文件的常量池的有效16位无符号索引。表示当前属性的名字。
attribute_length
项的值给出了跟随其后的字节的长度,这个长度不包括attribute_name_index
和attribute_name_index
项的6个字节。
14.1 ConstantValue属性
ConstantValue属性是定长属性,位于field_info结构的属性表中。如果该字段为静态类型(即field_info结构的access_flags项设置了ACC_STATIC标志),则说明这个field_info结构表示的常量字段值将被分配为它的ConstantValue属性表示的值,这个过程也是类或接口申明的常量字段(Constant Field)初始化的一部分。这个过程发生在引用类或接口的类初始化方法执行之前。
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
attribute_name_index
项的值,必须是一个对常量池的有效索引。 attribute_length
项的值固定为2。 constantvalue_index
项的值,必须是一个对常量池的有效索引。
14.2 Code属性
Code属性是一个变长属性,位于method_info结构的属性表。一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。所有Java虚拟机实现都必须能够识别Code属性。如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它情况下,method_info有必须有明确的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];
}
attribute_name_index
项的值必须是对常量池的有效索引 attribute_length
项的值表示当前属性的长度,不包括开始的6个字节。 max_stack
项的值给出了当前方法的操作数栈在运行执行的任何时间点的最大深度。 max_locals
项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。long和double型的局部变量的最大索引是max_locals-2,其它类型的局部变量的最大索引是max_locals-1. code_length
项给出了当前方法的code[]数组的字节数,code_length的值必须大于0,即code[]数组不能为空。 code[]
数组给出了实现当前方法的Java虚拟机字节码。 exception_table_length
项的值给出了exception_table[]
数组的成员个数量。 exception_table[]
数组的每个成员表示code[]
数组中的一个异常处理器(Exception Handler)。exception_table[]
数组中,异常处理器顺序是有意义的(不能随意更改)。 start_pc
和end_pc
两项的值表明了异常处理器在code[]数组中的有效范围。 handler_pc
项表示一个异常处理器的起点
如果catch_type
项的值不为0,那么它必须是对常量池的一个有效索引 attributes_count
项的值给出了Code属性中attributes表的成员个数。
属性表的每个成员的值必须是attribute结构。一个Code属性可以有任意数量的可选属性与之关联。
14.3 StackMapTable属性
StackMapTable属性是一个变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的类型阶段被使用。
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
attribute_name_index
项的值必须是对常量池的有效索引 attribute_length
项的值表示当前属性的长度,不包括开始的6个字节。 number_of_entries
项的值给出了entries
表中的成员数量。Entries表的每个成员是都是一个stack_map_frame
结构的项。 entries
表给出了当前方法所需的stack_map_frame
结构。
…更多的属性就不在这一一贴了,太多了,需要的时候查官方文档即可:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4
字节码指令
java虚拟机指令由一个字节长度的,代表某种特定操作含义的数字(称之为操作码),以及随后的代表此操作所需参数的操作数而构成。
操作码的长度为1个字节,所以最大只有256条
常量入栈指令
局部变量值转载到栈中指令
将栈顶值保存到局部变量中指令
wide指令
通用(无类型)栈操作指令
类型转换指令
整数运算
浮点运算
逻辑运算——移位运算
逻辑运算——按位布尔运算
控制流指令——条件跳转指令
控制流指令——比较指令
控制流指令——无条件跳转指令
控制流指令——表跳转指令
控制流指令——异常和finally
对象操作指令
数组操作指令
方法调用指令
方法返回指令
线程同步指令
指令参考:https://blog.csdn.net/web_code/article/details/12164733
一个简单的demo分析
Test.java
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a+b;
System.out.println(c);
}
}
javap -v Test.class
#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #26.#27 // java/io/PrintStream.println:(I)V
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10 //把10扩展成int入栈
2: istore_1 //将栈顶int类型值保存到局部变量1中
3: bipush 20 //把20扩展成int入栈
5: istore_2 //将栈顶int类型值保存到局部变量2中
6: iload_1 //从局部变量1中装载int类型值入栈
7: iload_2 //从局部变量2中装载int类型值入栈
8: iadd // 将栈顶两int类型数相加,结果入栈。
9: istore_3 //将栈顶int类型值保存到局部变量3中
10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;获取静态字段的值。#2表示常量池的索引
13: iload_3
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 运行时方法绑定调用方法。
17: return //void函数返回。