Java虚拟机-类文件结构

代码编译的结果从本地机器码转为字节码;
 
无关性的基石:
各个不一样平台的虚拟机和全部平台统一使用的程序存储格式-字节码,是构成平台无关性的基石;
实现语言无关性的基础仍然是虚拟机和字节码存储格式;
Java虚拟机不和包括Java在内的任何语言绑定,它只和class文件这种特定的二进制文件所关联;
Class文件中包含了Java虚拟机指令集和符号表以及若干其余辅助信息。
基于安全方面的考虑,Java虚拟机规范要求在Class文件中使用许多强制性的语法和机构化约束,但任何一门功能性语言均可以表示为一个能被Java虚拟机所接受的有效的Class文件。

Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎所有是程序运行的必要数据,没有空隙存在。
当遇到须要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
 
根据Java虚拟机规范的规定,Class文件格式采用一种相似于C语言结构体的伪结构来存储数据,这种 伪结构中只有两种数据类型:无符号数和表
 
无符号数
无符号数属于基本数据类型,以u一、u二、u四、u8分别表明1个字节、2个字节、4个字节和8个字节的无符号数;
无符号数能够用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
 
表:
表是由多个无符号数或者其余表做为数据项构成的复合数据类型,全部表都习惯以“ _info ”为结尾。
表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表;
 
Class结构不像XML等描述语言,因为它没有任何分隔符号;

 
 
魔数和Class文件版本:
魔数(magic) :每一个Class文件的头4个字节成为魔数,它惟一的做用是肯定整个文件是否为一个能被虚拟机接受的Class文件。
Class文件的魔数值是 0xCAFEBABE
Class文件版本 :第5和第6个字节是次版本号(Minor Version)。第7和第8个字节是主版本号(Major Version)。
Java的版本号是从45开始的,JDK1.1以后的每一个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容之前版本的Class文件,但不能运行之后版本的Class文件。(即便文件格式并未发生任何改变,虚拟机也必须拒绝执行超过其版本号的Class文件)。
 
常量池:
紧挨着主次版本号的就是常量池入口;
常量池能够理解为Class文件之中的资源仓库,它是Class文件结构中与其余项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它仍是在Class文件中第一个出现的表类型数据项目。
常量池容量计数器: 由于常量池中常量的数量不固定,因此在常量池的入口须要放置一项u2类型的数据,表明 常量池容量计数器,是从1开始计数
常量池容量计数器,0x0016,十进制为22,表明有21个常量。
Class文件结构中只有常量池的容量计数是从1开始,对于其余集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都是与通常习惯相同,从0开始计数。
 
常量池 中主要存放两大类常量: 字面量(Literal)和符号引用(Symbolic References)
字面量 :比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等;
符号引用 则属于编译原理方面的概念,包括下面三类常量:
一、类和接口的全限定名;
二、字段的名称和 描述符
三、方法的名称和描述符
当虚拟机运行时,须要从常量池得到对应的符号引用,再在类建立时或运行时解析、翻译到具体的内存地址之中。

常量池中每一项常量都是一个表,共有11种,表开始的第一位都是一个本身的标志位,代表这个常量属于哪一种类型。

Java程序中不能定义超过64KB英文字符的变量和方法名,不然没法编译。
使用JAVAP工具能够分析Class文件字节码:javap -verbose TestClass
 
访问标志:
在常量池结构以后,紧接着的2个字节表明访问标志(access_flags), 这个标志用于识别一些类或者接口层次的访问信息
包括:这个Class是类仍是接口;是否认义为public类型;是否认义为abstract类型;若是是类,是否被声明为final类型等;
访问标志中一共有16个标志位可使用;
 
类索引、父类索引与接口索引集合:用来肯定集成关系
类索引 :一个u2类型的数据,用于肯定整个类的全限定名。
父类索引 :一个u2类型的数据,用于肯定这个类的父类的全限定名。因为Java不容许多重集成,因此父类索引只有一个。
接口索引集合 :一组u2类型的数据集合,用于描述这个类实现了哪些接口。
对于接口索引集合,入口的第一项-u2类型的数据为接口计数器,表示索引表的容量。若是该类没有实现任何接口,则该接口计数器值为0,后面接口的索引表再也不占用任何字节;
 
字段表集合
字段表(field_info),用于描述接口或者类中声明的变量。
字段(field)包括类级变量以及实例级变量,可是不包含方法内声明的局部变量。
描述该字段是不是public、private、protected、static、final、volatile等;

 
简单名称 :指没有类型和参数修饰的方法或者字段名称;
 
描述符 :用来描述字段的数据类型,方法的参数列表和返回值。
基本数据类型以及表明无返回值的void类型用一个大写字母表示;
对象类型用字符L加对象的全限定名来表示;


方法表集合:描述方法
Class文件存储格式中对方法的描述和对字段的描述几乎采用了彻底一致的方式,方法表的结构如同字段表同样。
包括访问标志、名称索引、描述符索引、属性表集合;
 
方法中的代码存储在方法属性表集合中一个名为Code的属性中;


在Java虚拟机规范中,要重载一个方法,除了要与原方法具备相同的简单名称外,还要求必须拥有一个与原方法不一样的特征签名,特征签名在java代码层面和字节码层面有不一样的定义。
代码层面的签名只包括方法名称,参数顺序以及参数类型;
字节码正面还包括方法返回值和异常表。
所以在Class文件中,若是两个方法有相同的名称和特征签名,可是返回值不一样,也是合法的。
 
属性表集合
属性表集合的限制较少,不要求各个属性表有严格的顺序,而且只要不和已有的属性名重复,任何人实现的编译器均可以向属性表中写入本身定义的属性信息;
Java虚拟机在运行时会忽略掉它不认识的属性。


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

max_stack: 虚拟机运行的时候会根据它的值来分配栈帧中的操做栈深度;
max_locals:单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位;
code_length+code:用来存储Java源程序编译后生成的字节码指令;
code_length:表示字节码长度
code:用于存储字节码的一系列字节流。
虚拟机规范中明确限制一个方法不容许超过 65535 条字节码指令。若是超过了,Java编译器也会拒绝编译。
在任何实例方法中,均可以经过this关键字访问到此方法所属对象,它是经过Javac编译器编译的时候把对this关键字的访问转变为对一个普通方法参数的访问。
 
Exceptions属性
做用:列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常;
 
LineNumberTable属性
做用:用于描述java源码行号与字节码行号之间的对于关系。不是运行时必需的属性,可是会默认生成到Class文件中;
javac中可使用-g:none取消,或使用 -g:lines 来要求生成这项信息。
 
LocalVariableTable属性
做用:描述栈帧中的局部变量表中的变量与Java源码中定义的变量之间的关系。不是运行时必需的属性。
javac中可使用-g:none取消,或使用 -g:vars 来要求生成这项信息;
 
SourceFile属性
做用:用于记录生成这个Class文件的源码文件名称。可选。
javac中可使用-g:none取消,或使用 -g:source 来要求生成这项信息;
 
ConstactValue属性
做用:通知虚拟机自动为静态变量赋值;只有被static关键字修饰的变量(类变量)才可使用这个属性;
对于非静态变量,则是在实例构造器<init>方法中进行;
目前Sun Javac编译器中,对于final修饰的静态变量,而且这个变量的数据类型是基本数据类型或String,就使用ConstactValue属性来进行初始化;
若是仅仅是静态变量,未使用final修饰,那么就是在实例构造器<clinit>方法中进行初始化;
 
虚拟机规范中并未要求字段必须设置ACC_FINAL标识。对final关键字的要求是javac编译器本身加入的限制。
 
InnerClasses属性
做用:用于记录内部类与宿主类之间的关联;
 
Deprecated及Synthetic属性
Deprecated做用:表示某个类、字段或方法,已经被程序做者定位再也不推荐使用。
Synthetic做用:表示此字段或方法并非由Java编码直接产生的,而是由编译器自行添加的。JDK1.5之后;
如this字段和实例构造器,类构造器等;
 
StackMapTable属性
JDK1.6发布后增长到Class文件规范中的,是一个复杂的变长属性,位于Code属性的属性表中。
 
Signature属性
JDK1.5发布后增长到Class文件规范中的,是一个可选的定长属性,能够出现于类、属性表和方法表结构的属性表中;
 
BootstrapMethods属性
JDK1.7发布后增长到Class文件规范中的,是一个复杂的变长属性,位于类文件的属性表中
 
 
字节码指令简介
Java虚拟机的指令由一个字节长度的,表明着某种特定操做含义的数字(操做码)以及跟随其后的零至多个表明此操做所需参数(操做数)而构成。
因为Java虚拟机采用面向操做数栈而不是寄存器的架构,因此大多数的指令都不包含操做数,只有一个操做码。
 
字节码指令集的优缺点:
缺点
一、因为限制Java虚拟机操做码的长度是一个字节(0~255),意味着指令集的操做码总数不超过256条;
二、因为Class文件格式放弃了编译后代码的操做数长度对齐,意味着虚拟机处理那些超过一个字节数据的时候,不得不在运行时从字节中重建出具体数据的结构;
优势:
一、放弃操做数长度对齐,意味着能够省略不少填充和间隔符号;
二、用一个字节来表明操做码,也是为了尽量获取短小精干的编译代码;
 
这种追求尽量小数据量,高传输效率的设计是由Java语言涉及之初面向网络、智能家电的技术背景决定的,并一直沿用到至今。
 
字节码与数据类型
 
加载和存储指令
做用:用于将数据在栈帧中的局部变量表和操做数栈之间来回传输;
这类指令包含以下以下内容:
一、将一个局部变量加载到操做栈;***load
二、将一个数值从操做数栈存储到局部变量表;***store
三、将一个常量加载到操做数栈;***const
四、扩充局部变量表的访问索引的指令:wide;
存储数据的操做数栈和局部变量表主要就是由加载和存储指令进行操做;
 
运算指令
运算或算数指令用于对两个操做数栈上的值进行某种特定运算,并把结果从新存入到操做数栈顶。
算数大体分为:对整型数据进行运算的指令和对浮点型数据进行运算的指令;
Java虚拟机要求在进行浮点数的运算时,全部的运算结果都必须舍入到适当的精度,舍入模式是IEEE754规范中的默认舍入模式,称为向最接近数舍入模式;处理的时候不会抛出任何运行时异常;
在将浮点数转为整数时,Java虚拟机使用IEEE754标准的向零舍入模式,结果会致使数字备截断,全部小数部分的有效字节都会备丢弃;
 
类型转换指令
类型转换指令能够将两种不一样的数值类型进行相互转换,这些转换操做通常用于实现用户代码中的显式类型转换操做。
宽化类型转换、窄化类型转换。
 
对象建立与访问指令
对象建立后,就可使用对象访问指令获取对象实例或者数组实例中的字段或者数组元素。
指令以下:
     1)建立实例的指令:new
 
    2)建立数组的指令:newarray,anewarray,multianewarray
 
    3)访问字段指令:getfield,putfield,getstatic,putstatic
 
    4)把数组元素加载到操做数栈指令:baload,caload,saload,iaload,laload,faload,daload,aaload
 
    5)将操做数栈的数值存储到数组元素中执行:bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
 
    6)取数组长度指令:arraylength
 
    7)检查实例类型指令:instanceof,checkcast
 
 
操做数栈管理指令
 
用于直接操做操做数栈的指令,包括:
 
    1)将操做数栈的栈顶一个或两个元素出栈:pop、pop2
 
    2)复制栈顶的一个或两个数值并将复制值活着双份的复制值从新压入栈顶:dup、dup二、dup_x一、dup2_x1
、dup_x2
、dup2_x2
 
    3)将栈最顶端的两个数值互换:swap
 
 
控制转移指令
让JVM有条件或无条件从指定指令而不是控制转移指令的下一条指令继续执行程序。控制转移指令包括:
    1)条件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt等
    2)复合条件分支:tableswitch,lookupswitch
    3)无条件分支:goto,goto_w,jsr,jsr_w,ret
各类类型的比较最终都会转化为int类型的比较操做;
 
方法调用和返回指令
方法调用指令与数据类型无关,可是方法返回指令是根据返回值的类型区分的;
 
异常处理指令
在java程序中显式抛出异常的操做是由athrow指令来实现;
 
同步指令
方法级的同步是隐式的,无需经过字节码指令来控制,它实如今方法调用和返回操做中。
虚拟机从方法常量池中的方法标结构中的 ACC_SYNCHRONIZED标志区分是不是同步方法。方法调用时,调用指令会检查该标志是否被设置,若设置,执行线程持有moniter,而后执行方法,最后完成方法时释放moniter。
同步一段指令集序列,一般由synchronized块标示,JVM指令集中有monitorenter和monitorexit来支持synchronized语义。
结构化锁定是指方法调用期间每个monitor退出都与前面monitor进入相匹配的情形。JVM经过如下两条规则来保证结结构化锁成立(T表明一线程,M表明一个monitor):
     1)T在方法执行时持有M的次数必须与T在方法完成时释放的M次数相等
     2)任什么时候刻都不会出现T释放M的次数比T持有M的次数多的状况

虚拟机实现的方式主要有如下两种:
一、将输入的Java虚拟机代码在加载或执行时翻译成另一种虚拟机的指令集;
二、将输入的Java虚拟机代码在加载或执行时翻译成宿主机CPU的本地指令集(JIT代码生成技术)
相关文章
相关标签/搜索