Class文件存储的内容称为字节码(ByteCode),包含了JVM指令集和符号表以及若干其余辅助信息。html
class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何分隔符,整个Class文件中存储的内容几乎所有是程序运行的必要的数据,没有空隙存在。java
当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。web
Class文件中有两种数据类型,分别是无符号数和表。shell
无符号数属于基本的数据类型,以u一、u二、u四、u8来表示一个字节、两个字节...的无符号数;无符号数用来描述数字、索引引用、数量值或UTF-8编码构成的字符串值。bootstrap
表是由多个无符号数或其余表做为数据项构成的复合数据类型,通常以"_info"结尾,用来描述class文件的数据结构。数组
特色:节省存储空间,提升处理性能bash
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];
}
复制代码
u2表示无符号数2个字节 u4表示无符号数4个字节数据结构
魔数的惟一做用是肯定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。oracle
建立一个class文件 magic.class ,内容是magic test,直接运行java magic
操做:jvm
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 查看:
魔数日后后面四位:表示字节码版本,分别表示Class文件的副、主版本。当今用的最广的几个版本: jdk1.8:52 jdk1.7:51 jdk1.6:50
版本向下兼容
常量池计数器,值等于constant_pool表中的成员数加1,占用两个字节
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 |
表示类或接口
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
复制代码
name_index
必须是对常量池的一个有效索引
字段:
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
中给定的描述符必须是方法
描述符。
用来表示String的结构
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
复制代码
string_index
必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,表示一组Unicode码点序列,这组Unicode码点序列最终会被初始化为一个String对象。
表示4字节(int和float)的数值常量:
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
复制代码
表示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个结构不一样,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结构,这个结构表示一个有效的字段描述符或方法描述符。
用于表示字符串常量的值
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
复制代码
CONSTANT_Utf8_info结构中的内容是以length
属性肯定长度的
表示方法句柄
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
复制代码
reference_kind
项的值必须在1至9之间(包括1和9),它决定了方法句柄的类型。
reference_kind
项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic)
,那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段建立的方法句柄。reference_kind
项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial)
,那么常量池在reference_index
索引处的项必须是CONSTANT_Methodref_info
结构,表示由类的方法或构造函数建立的方法句柄。reference_kind
项的值是9(REF_invokeInterface)
,那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法建立的方法句柄。reference_kind
项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface)
,那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。reference_kind
项的值是8(REF_newInvokeSpecial)
,那么方法句柄对应的方法必须为实例初始化()方法。表示方法类型
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
复制代码
表示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
结构,表示方法名和方法描述符。
访问标志,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 | 标识枚举类型 |
this_class的值必须是对constant_pool表中项目的一个有效索引值。
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
表示这个Class文件所定义的类的直接父类,若是Class文件的super_class的值为0,那这个Class文件只多是定义的是java.lang.Object类,只有它是惟一没有父类的类
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
表示有这个类有几个接口。
成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)同样,即interfaces[0]对应的是源代码中最左边的接口。
是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。
表示当前类或接口的直接父接口数量
表示当前Class文件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结构
,一个字段能够有任意个关联属性。
methods_count的值表示当前Class文件methods[]数组的成员个数,Methods[]数组中每一项都是一个method_info结构的数据项。
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
结构,一个方法能够有任意个与之相关的属性。
attributes表中每一项都是一个attribute_info结构的数据项。
attributes_count的值表示当前Class文件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个字节。
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
项的值,必须是一个对常量池的有效索引。
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属性能够有任意数量的可选属性与之关联。
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条
指令参考:https://blog.csdn.net/web_code/article/details/12164733
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a+b;
System.out.println(c);
}
}
复制代码
#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函数返回。
复制代码