您的位置:首页 > 其它

JVM之类文件结构

2016-09-17 11:42 169 查看
Class类文件的结构
魔数Magic Number与Class文件的版本Version

常量池Constant Pool

访问标志Access Flags

类索引父类索引与接口索引集合This ClassSuper Class Interfaces

字段表集合Fields

方法表集合Methods

属性表集合Attributes

字节码指令
加载和存储指令

运算指令

对象创建与访问指令

操作数栈管理指令

控制转移指令

方法调用和返回指令

异常处理指令

同步指令

公有设计私有实现

JVM不和Java在内任何语言绑定,只与class文件,这种特定的二进制文件格式关联。Class字节码文件中包含虚拟机指令集和符号表,以及若干其它辅助信息

无关性的基石

虚拟机

字节码存储格式

虚拟机发展到JDK 1.7~1.8的时候,JVM设计者通过JSR-292实现了其他语言运行与JVM上



Class类文件的结构

class文件是一组以8位字节为基础单位的二进制流,紧凑,中间无分隔符。

当遇到需要占用8位字节以上空间的数据项,按Big-Endian方式分割成若干个8位字节

Class文件格式,是一种类似于C语言结构体的伪结构来存储数据。这种伪结构只有两种数据类型

无符号数
可以描述数字、索引引用、数量值、按照UTF-8编码构成字符串值


描述有层次关系的符合结构的数据。多个无符号数或者其他表作为数据项构成的复合数据类型,所有表,习惯性地以”_info”结尾

无符号数包括:

u1–>1 byte(8 bits)

u2–>2 byte2(16 bits)

u4–>4 bytes(32 bits)

u8–>8 bytes(64 bits)

表包括:

多个无符号数

其他表

整个Class文件,本质上就是一张表

结构总览

Magic Number

Version

Constant Pool

Access Flags

This Class Name

Super Class Name

Interfaces

Fields

Methods

Attributes



魔数(Magic Number)与Class文件的版本(Version)

每个字节码文件的头4个字节,一定是0xCAFEBABE,这个数值叫做Magic Number

u4这一无符号数(是一种数据类型,看上文)可以表示magic

作用
魔数的唯一作用,就是确定这个文件是否为一个能被虚拟机接收的字节码文件

紧接着4个字节的魔数之后,第5和第6个字节是次版本号(Minor Version)

第7和第8个字节是Class文件的主版本号(Major Version)

常量池(Constant Pool)

接着第8个字节的,是常量迟Constant Pool的入口

可以理解為Class文件中的资源仓库

与其他项目关联最多的数据类型

占用Class文件空间最大的数据类型

Class文件中第一个出现的表类型数据项目

由于常量池数量不固定,所以在入口,需要一项u2类型的计数器,constant_pool_count,这是第9个字节

这个技术器跟习惯的不一样,从1开始算,不是00带表”不引用任何一个常量池项目”

存放数据类型:

字面量(literal).

文本字符串

final修饰的常量

符号引用(Symbolic References),编译原理方面的概念

类和接口的全限定名(Fully Qualified Name)

字段的名称和描述符(Descriptor)

方法的名称和描述符

Java代码进行javac编译时,不像C/C++那样,有”连接”的步骤。

而是在虚拟机加载class文件的时候,进行动态连接。即,Class文件不会保存各个方法、字段的最终内存布局信息。因此,这些字段、方法的引用,不经过运行期转换的话,无法得到真正内存的入口地址,也就无法直接被虚拟机使用

常量池中的每一个常量,都是表

JDK 1.7 为更好支持 动态语言调用,新增
CONSTANT_MethodHandle_info,CONSTANT_MethodType_info,CONSTANT_InvokeDynamic_info






表的结构组成

常量池的表除了tag,还有自己不同的结构

u1类型的标志位tag,取值见上表

各自类型自己的结构

例如:

CONSTANT_MethodHandle_info


- tag(u1类型)

- name_index(u2类型) 索引值,指向
CONSTANT_Utf8_info
,代表类或接口的全限定名

class文件中,方法和字段,都需要引用
CONSTANT_Utf8_info
型常量来描述名称,所以utf8_info常量的最大长度,就是Java中方法、字段名的最大长度,u2类型,2个字节,最大值为65535。所以,当定义了超过64K英文字符的变量或方法名,JVM无法编译

JDK的bin目录下有个javap工具,是Oracle为我们准备的查看class文件的工具,运行例子如下图



访问标志(Access Flags)

常量池结束后,接着,是2个字节的访问标志
access_flags


access_flags一共有16个标志位,当前只定义了8个

这个标志用于识别访问权限、访问层次的信息,例如:

这个class是类还是接口

是否public类型

是否abstract

如果是类,是否声明为final

下图表



类索引、父类索引与接口索引集合(This Class,Super Class, Interfaces)

访问标志之后,是类索引(this_class)、父类索引(super_class)、接口索引集合(interfaces)。前面两个是u2类型数据,接口则是u2类型数据的集合

class文件由这3项数据来确定这个类的继承关系

除了java.lang.Object外,所有类的父类索引都不为0

类索引/父类索引–>持有2个u2类型–>指向CONSTANT_Class_info的类描述符常量–>找到定义在Utf_info中的全限定名

接口索引集合,入口的第一项,是u2类型的数据,接口计数器(interfaces_count),表示索引的容量,如果类没有实现接口,那么计数器的值为0

字段表集合(Fields)

字段表(field_info)用于描述接口,或者类中声明的变量

字段表结构

access_flag(u2,分清上面的对类定义的access_flag)

name_index(u2)

descriptor_index(u2)

attributes_count(u2)

attributes(attribute_info)

field包括类变量和实例变量,但不包括方法内部声明的局部变量

Java中描述一个字段(field)的标签

作用域(public,protect,default,private)

类变量还是实例变量(static)

可变性(final)

并发可见性(volatile,是否强制从主存读写)

是否参与序列化(transient)

类型(primitive,reference,array)

名称(name)

名称、数据类型适合引用常量池的常量来描述,而其他剩下的,适合用布尔值描述

字段表包含的固定数据项目,到descriptor_index就结束了

下图



方法表集合(Methods)

理解了上面的字段表的内容,接下来,方法表可以如法炮制。

Java中描述一个方法的标签

作用域(public,protect,default,private)

类方法还是实例方法(static)

可变性(final)

并发同步性(synchronized)

是否严格精度(strictfp)

是否抽象(abstract)

是否native(native)

名称(name)

方法表结构

access_flag(u2)

name_index(u2)

descriptor_index(u2)

attributes_count(u2)

attributes(attribute_info)

方法没有volatile和transient关键字,所以access_flag就没有这两个标志了,跟方法对应的,增加6个标志

标志名称标志值含义
ACC_SYNCHRONIZED0x0020是否为synchronized
ACC_BRIDGE0x0040是否是由编译器产生的桥接方法
ACC_VARARGS0x0080是否接受不定参数
ACC_NATIVE0x0100是否为native
ACC_ABSTRACT0x0400是否为abstract
ACC_STRICPFP0x0800是否为strictfp
方法里的代码,经过编译器编译成字节码指令,存放在方法属性表集合的”code”属性中

重载overload一个方法,除了要有与原方法相同的简单名称之外,还必须有一个与原方法不同的特征签名特征签名,是一个方法中,各个参数在常量池中的字段符号的引用的集合,也就是说,方法参数列表要不一样

属性表集合(Attributes)

属性表attribute_info,在class文件、字段表、方法表内,都可以存在,描述某些场景专有的信息

属性表的数据项目比较多,这里列出一些重要的

名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final定义的常量值
Exceptions方法表方法抛出的异常
EnclosingMethod类文件仅当一个类为内部类,或匿名类时才有的属性,用于标识包着这个类的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
BootStrapMethods类文件JDK 1.7新增,保存invokedynamic指令引用的引导方法限定符
属性表结构

attribute_name_index(u2)

attribute_length(u4)

info(u1)

Code属性

Java方法体中的代码,经过编译器处理后,最终变为字节码指令,存储在Code属性中

构成

attribute_name_index(u2)

指向CONTANT_Utf8_info类型常量索引,值为”Code”,

attribute_length(u4)

max_stack

代表操作数栈(Operand Stacks)深度的最大值

虚拟机根据这个值来分配栈帧中的操作栈

max_locals

局部变量表所需的存储空间

存储基本单位是Slot(32位)

局部变量表中的Slot可重用

方法参数、方法体中定义的局部变量

code_length

code

exception_table_length

exception_table

attributes_count

attributes

ConstantValue属性

只有类变量/静态变量才有的属性。虚拟机对类变量和实例变量的赋值方式以及时机有所不同

实例变量的赋值

在实例构造器中方法中进行

类变量的赋值

可以在类构造器方法

使用ConstantValue属性

字节码指令

Java虚拟机的指令构成:

1个字节长度的操作码(Opcode)

0到多个操作数(Oprands,代表此操作所需参数)

採用面向操作数栈,而不是寄存器的架构

Java虚拟机的解释器,基本执行模型

do {
自动计算PC寄存器的值,+1;
根据PC寄存器的指示位置,从字节码流中取出操作码;
if (字节码存在操作数)
从字节码流中取出操作数;
执行操作码定义的操作
} while(字节码流长度 > 0)


加载和存储指令

用于 栈帧中的局部变量表和操作数栈 之间的来回传输

将1个局部变量加载到操作栈

iload, iload_<n>,lload,lload_<n>,fload,fload_<n>,dload,dload_<n>,aload,aload_<n>


将1个数值从操作数栈存储到局部变量表

istore,istore_<n>,lstore,lstore_<n>,fstore,ftore_<n>,dstore,dstore_<n>,astore,astore_<n>


将1个常量加载到操作数栈

bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>


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

wide


运算指令

运算或算术指令,是对两个操作数栈上的值,做某种特定运算,并将运算结果重新存入操作数栈的栈顶

没有直接byte,char,short,boolean类型的算术指令,用操作int类型的指令代替

整型与浮点型在溢出和被0整除时,有不同行为表现

指令包括
加、减、乘、除、求余、取反、位移(i,l)、按位或、按位与、按位异或、局部变量自增(i)、比较(d,f,l)

对象创建与访问指令

虽然类实例和数组都是对象,但虚拟机对这两者的创建与操作,使用了不同的字节码指令

创建类实例的指令:
new


创建数组:
newarray
,
anewarray
,
multianewarray


访问类变量和实例变量:
getfield
,
putfield
,
getstatic
,
putstatic


将1个数组元素加载到操作数栈:
baload
,
caload
,
saload
,
iaload
,
laload
,
faload
,
daload
,
aaload


将1个操作数栈的值存储到数组元素中的指令:
bastore
,
castore
,
sastore
,
iastore
,
fastore
,
dastore
,
aastore


取得数组长度:
arraylength


检测类实例类型:
instanceof
checkcast


操作数栈管理指令

操作数栈的操作,无非是压栈、弹栈

将操作数栈的栈顶1个或2个元素出栈:
pop
pop2


复制栈顶1个或2个数值,并将复制值或2份复制值,重新压入栈顶:
dup
dup2
dup_x1
,
dup2_x1
,
dup_x2
,
dup2_x2


栈的最顶端两个数值互换:
swap


控制转移指令

使得Java虚拟机有条件,或无条件,从指定的位置指令,继续执行程序,而不是控制转移指令的下一条指令。

概念模型上,可以理解为,在有条件/无条件地修改PC寄存器的值

条件分支:
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


方法调用和返回指令

invokevirtual
: 调用对象实例方法

invokeinterface
:调用接口方法

invokespecial
:调用需要特殊处理的实例方法,如实例初始化方法、私有方法、父类方法

invokestatic
:调用类方法

invokedynamic
:运行时,动态解析出调用点限定符所引用的方法,并执行该方法;分派逻辑由用户设定的引导方法决定

异常处理指令

使用异常表

同步指令

Java虚拟机支持方法级别的同步,以及某段代码(指令序列)的同步,通过管程(Monitor)来支持,叫监视器不挺好的嘛。

方法级别的同步是隐式的,无须通过字节码指令来控制,方法的常量池的方法表结构中的访问标志ACC_SYNCHRONIZED,可以得知,方法是否为同步方法

同步代码块,需要
monitorenter
,
monitorexit
2条关键指令

公有设计,私有实现

虚拟机的实现方式,主要有

将输入的Java虚拟机代码,在加载或执行时,翻译成另外一种虚拟机的指令集

将输入的Java虚拟机代码,在加载或执行时,翻译成宿主机CPU的本地指令集,即JIT代码生成技术
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm class 结构