一、Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,当遇到8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行储存。Class文件中只有两种数据:无符号数和表。无符号数属于基本的数据类型,以u1、u2、u4、u8来分别表示1个字节、2个字节、4个字节和8个字节,无符号数可以用来描述数字、索引引用、数量值或者以UTF8编码的字符串。表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表习惯性的以_info结尾。Class文件结构如下所示。
类型名称数量
u4magic
魔数
1
u2minor_version 次版本号1
u2major_version 主版本号1
u2constant_pool_count 常量池容量计数值1
cp_infoconstant_pool 常量池constant_pool_count-1
u2access_flags 访问标志1
u2this_class 类索引1
u2super_class 父类索引1
u2interfaces_count 接口索引计数值1
u2interfaces 接口索引集合interfaces_count
u2fields_count 字段计数值1
field_infofields
字段表
fields_count
u2methods_count 方法计数值1
method_infomethods
方法表
methods_count
u2attributes_count 属性计数值1
attribute_infoattributes 属性表attributes_count
1、魔数与Class文件版本
每个Class文件头4个字节(Magic)为魔数,它的唯一作用就是确定这个文件是Class文件。第5第6个字节(minor_version)是次版本号,第7第8个字节(major_version)是主版本号。java的版本号是从45开始的。
2、常量池
紧接着版本号之后的是常量池入口,因为常量池的大小不确定所以在常量池入口定义一个常量池的容量计数(constant_pool_count),这个容量的计数是从1开始的。常量池的作用是存放字面量和符号引用。这些字段、方法和符号引用在类加载的时候具体分配内存地址。
常量池中每一项常量都是一个表,这些表的第一个字节是标志,具体类型如下表。
类型标志描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
常量池总表如下图所示
常量项目类型描述
CONSTANT_Utf8_infotagu1值为1
lengthu2UTF8编码的字符串占用的字节数
bytesu1长度为length的UTF8编码的字符串
CONSTANT_Integer_infotagu1值为3
tytesu4按照高位在前存储的int值
CONSTANT_Float_infotagu1值为4
tytesu4按照高位在前存储的float值
CONSTANT_Long_infotagu1值为5
tytesu8按照高位在前存储的long值
CONSTANT_Double_infotagu1值为6
tytesu8按照高位在前存储的double值
CONSTANT_Class_infotagu1值为7
indexu2指向权限定名常量项的索引
CONSTANT_String_infotagu1值为8
indexu2指向字符串字面量的索引
CONSTANT_Fieldref_infotagu1值为9
indexu2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符CONSTANT_NameAndType的索引项
CONSTANT_Methodref_infotagu1值为10
indexu2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符CONSTANT_NameAndType的索引项
CONSTANT_InterfaceMethodref_infotagu1值为11
indexu2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符CONSTANT_NameAndType的索引项
CONSTANT_NameAndType_infotagu1值为12
indexu2指向该字段或方法名称常量项的索引
indexu2指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_infotagu1值为15
reference_kindu1值必须在1-9之间,它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为
reference_indexu2值必须是对常量池的有效索引
CONSTANT_MethodType_infotagu1值为16
descriptor_indexu2值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_infotagu1值为18
bootstrap_method_attr_indexu2值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
name_and_type_indexu2值必须是对当前常量池的有效索引,常量池在该索引处必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符
3、访问标志
在常量池结束后,紧接着的两个字节代表的是访问标志(access_flags),这个标志用于识别一些类或者接口的描述,该值是按照访问标志的真假按照或运算的结果。具体访问标志如下表
标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语意
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志值为真其他类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举
4、类索引、父类索引与接口索引集合
Class文件中由类索引、父类索引和接口索引集合来确定类的继承关系。类索引用于确定这个类的权限定名,父类索引用于确定这个类的父类的权限定名。由于Java语言不允许多继承,所以父类索引只有一个。接口索引就描述了类实现了哪些接口。
类索引和父类索引各自指向一个类型为CONSTANT_Class_info的类描述符常量,在通过CONSTANT_Class_info找到CONSTANT_Utf8_info类型的常量中的权限定名字符串。
对于接口索引集合,先是u2类型接口计数器(interfaces_count),然后是总数为计数器个数的u2类型的接口集合
5、字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段包括类及变量以及实例级变量,单不包括方法内部声明的局部变量。字段包括的信息有:字段的作用域、是实例变量还是类变量、可变性、并发可见性、可否被序列化、字段数据类型、字段名称。字段表结构、字段访问标志、描述符字段标识含义表如下所示
类型名称数量
u2access_flags 访问标志,具体见下表1
u2name_index 字段名称1
u2descriptor_index 描述,具体见下表1
u2attributes_count 额外信息表技术值1
attribute_infoattributes 额外信息表attributes_count
标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRANSIENT0x0080字段是否transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生的
ACC_ENUM0x4000字段是否enum
标识字符含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V基本类型void
L对象类型
6、方法表集合
方法表与字段表的结构类似,具体见下表
类型名称数量
u2access_flags 访问标志,具体见下表1
u2name_index 字段名称1
u2descriptor_index 描述,具体见下表1
u2attributes_count 额外信息表技术值1
attribute_infoattributes 额外信息表attributes_count
标志名称标志值含义
ACC_PUBLIC0x0001方法是否public
ACC_PRIVATE0x0002方法是否private
ACC_PROTECTED0x0004方法是否protected
ACC_STATIC0x0008方法是否static
ACC_FINAL0x0010方法是否final
ACC_SYNCHRONIZED0x0020方法是否是synchronized
ACC_BRIDGE0x0040方法是否是由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100方法是否为native
ACC_ABSTRACT0x0400方法是否为abstract
ACC_STRICTFP0x0800方法是否strictfp
ACC_SYNTHETIC0x1000方法是否有编译器自动产生
7、属性表集合
属性表用于储存额外的信息,存在于Class文件、字段表和方法表。由于属性表内容过多,这里不详细介绍。
二、字节码指令简介
指令是由一个字节长度的、代表着某种特定操作含义的数字以及跟随其后的零至多个代表此操作所需参数而构成。对于大部分与数据类型相关的字节码指令,操作码助记符中都有特殊字符代表数据类型:i代表int、l代表long、s代表short、b代表byte、c代表char、f代表float、d代表double、a代表reference
1、加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间的来回传输,这类的指令包括如下内容:
将一个局部变量加载到操作栈:iload、iload_
将一个数值从操作数栈存储到局部变量表:istore、istore_
将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_
扩充局部变量表的访问索引的指令:wide
以
2、运算指令
运算或算数指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。大体上算数指令可以分为两种:对整型数据进行运算的指令与对浮点数据进行运算的指令,byte、short、char和boolean的算数指令由int类型的指令代替,所有算数指令如下
加法指令:iadd、ladd、fadd、dadd
减法指令:isub、lsub、fsub、dsub
乘法指令:imul、lmul、fmul、dmul
除法指令:idiv、ldiv、fdiv、ddiv
求余指令:irem、lrem、frem、drem
取反指令:ineg、lneg、fneg、dneg
位移指令:ishl、ishr、iushr、lshl、lshr、lushr
按位或指令:ior、lor
按位与指令:iand、land
按位异或指令:ixor、lxor
局部变量自增指令:iinc
比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
只有除法指令以及求余指令当出现除数是0时会导致ArithmeticException异常,其余任何整型运算场景都不应该出现这种异常。另外虚拟机在处理浮点数运算时,不会抛出任何异常,当一个操作产生溢出时,将会使用有符号的无穷大来表示,如果某个操作没有明确数学定义的话,将会使用一个NaN来表示。在对long数据进行比较时,采用带符号的比较方式,对浮点数值进行比较时,采用无信号的比较方式。
3、类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换。虚拟机直接支持以下数值类型的宽化类型转换(即小范围类型向大范围类型的安全转换)
int类型到long、float或者double类型
long类型到float、double类型
float类型到double类型
相对的,处理窄化类型转换必须使用显示的转换指令:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f,转换的过程可能导致结果产生不同的正负号、不同数量级和精度丢失。
4、对象创建与访问指令
虽然类实例和数组都是对象,但虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。对象创建后,就可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素,这些指令如下
创建类实例的指令:new
创建数组的指令:newarray、anewarray、multianewarray
访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic
把一个数组元素加载到操作数栈的指令:bload、caload、saload、iaload、laload、faload、daload、aaload
将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
取数组长度的指令:arraylength
检查类实例类型的指令:instanceof、checkcast
5、操作数栈管理指令
如同操作一个普通数据结构中的堆栈那样,虚拟机提供了一些用于直接操作操作数栈的指令,包括
将操作数栈的栈顶一个或两个元素出栈:pop、pop2
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
将栈最顶端的两个数值互换:swap
6、控制转移指令
控制转移指令可以让虚拟机有条件或无条件的从指定的位置指令而不是控制转移指令的下一条指令继续执行。控制转移指令如下
条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq、if_acmpne
复合条件分支:tableswitch、lookupswitch
无条件分支:goto、goto_w、jsr、jsr_w、ret
7、方法调用和返回指令
方法调用具体请看后续,这里只列举5条用于方法调用的指令
invokevirtual用于调用对象的实例方法,根据对象的实际类型进行分派、
invokeinterface用于调用接口方法,它会在运行时搜索一个实现了这个接口方法打击对象,找出合适的方法进行调用。
invokespecial用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法
invokestatic用于调用类方法
invokedynamic用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。
方法返回指令有:ireturn(当返回时boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn、和areturn,另外还有一条return指令的供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用
8、异常处理指令
虚拟机显示抛出异常都使用athrow指令实现,虚拟机还使用idiv或ldiv指令抛出运算中的ArithmeticException异常。
9、同步指令
虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。方法级的同步是常量表中的访问标志控制的。同一段指令集序列通常是由synchronized关键字来表示的,指令有monitorenter和monitorexit两条指令来支持的。