JVM3-类文件结构

一、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_、lload、lload_、fload、fload_、dlaod、dload_、aload、aload_

将一个数值从操作数栈存储到局部变量表:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_

将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_、fconst_、dconst_

扩充局部变量表的访问索引的指令:wide

结尾的指令,实际上是代表了一组指令(例如iload_,它代表了iload_0、iload_1、iload_2和iload_3这几条指令)。iload_0与操作数为0时的iload指令完全一致。

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两条指令来支持的。

你可能感兴趣的:(JVM3-类文件结构)