DepthJVM-类文件结构

来源:互联网 发布:sql server软件下载 编辑:程序博客网 时间:2024/06/06 12:21
1.无关性
平台无关性、语言无关性
各种平台虚拟机与所有平台统一使用的程序存储格式--字节码是构成平台无关性的基石。Java虚拟机不与任何语言绑定,只与"Class文件"这种特定的二进制文件相关联,任何语言都可以表示为一个能被Java虚拟机所接受的有效Class文件,虚拟机不关心Class文件的来源是何种语言
2.Class类文件结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有任何分隔符。当遇到需要占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储
Class文件中只有两种数据类型:无符号数、表
无符号数属于基本数据类型,分别以u1、u2、u4、u8代表1、2、4、8个字节的无符号数
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质上就是一张表
类型名称数量u4magic1u2minor_version1u2major_version1u2constant_pool_count1cp_infoconstant_poolconstant_poop_count-1u2access_flags1u2this_class1u2super_class1u2interfaces_count1u2interfacesinterfaces_countu2fields_count1field_infofieldsfields_countu2methods_count1method_infomethodsmethods_countu2attributes_count1attribute_infoattributesattributes_count
2.1 magic(魔数)
唯一做用是确定整个文件是否为一个能被虚拟机接受的Class文件,用魔数进行识别主要是基于安全方面考虑。Class文件的魔数为:0xCAFEBABE。git或jpeg等文件头中都存在魔数
2.2 minor_version(次版本号)、major_version(主版本号)
Java版本号从45开始,JDK从1.1开始,高版本能向下兼容低版本的Class文件,但不能运行向上版本的Class文件,即时文件格式未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。JDK1.1支持45.0~45.65535的Class文件,无法执行46.0的Class文件;JDK1.2支持45.0~46.65535的Class文件,JDK1.7可生成Class文件的最大版本号位51.0
2.3 constant_pool_count(常量池容量计数)、constant_pool(常量池)
constant_pool_count从1开始,表示常量池中常量的数量,0x0016=22表示索引1~21的21项常量,第0项常量空出来的目的是在于满足后面某些指向常量池索引值的数据表达“不引用任何一个常量池项目”的含义。
常量池主要存放:字面量、符号引用
字面量比较接近于Java语言层面的常量概念,如文本字符串、final常量
符号引用包括:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

2.4 access_flag(访问标志)
主要用于识别一些类或接口层析的访问信息,包括:这个Class是类还是接口、是否为public、是否为abstract、类是否为final等。access_flags中一共有16个标志位可以使用,当前只定义了其中的8个,没有用到的一律为0

2.5 this_class(类索引)、super_class(父类索引)、interfaces_count+interfaces(接口索引集合)
Class文件根据这三项数据确定类的继承关系。类索引用于确定类的全限定名,父类索引用于确定类的父类全限定名(除了java.lang.Object外,所有Java类的父类索引都不为0),接口索引集合用于描述类实现了哪些接口(按照implements从左到右排序)
类索引和父类索引各自指向一个类型为CONSTANT_Class_info的类描述符常量
2.6 fields_count+fields(字段表集合)
字段表结构:access_flag(u2*1)、name_index(u2*1)、descriptor_index(u2*1)、attributes_count(u2*1)、attributes(attribute_info*attributes_count)
字段表(field_info)用于描述接口或类中声明的变量。字段包括:类级变量、实例级变量,但不包括局部变量。一个字段包括的信息:标志位表示【作用域(访问修饰符)、实例变量还是类变量(static)、可变性(final)、并发可见性(volatile)、可否被序列化(transient)】、常量表示【数据类型、字段名称】
access_flags:ACC_PUBLIC(0x0001)、ACC_PRIVATE(0x0002)、ACC_PROTECTED(0x0004)、ACC_STATIC(0x0008)、ACC_FINAL(0x0010)、ACC_VOLATILE(0x0040)、ACC_TRANSIENT(0x0080)、ACC_SYNTHETIC(0x1000,是否编译器自动产生)、ACC_ENUM(0x4000)
name_index:指向一个CONSTANT_Utf8_info类型的字符串,表示常量表中第n项的值
descriptor_index:指向一个CONSTANT_Utf8_info类型的字符串,表示常量表中第n项的值。用描述符来表述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照顺序放在"()"内,其中标识字符有byte(B)、char(C)、double(D)、float(F)、int(I)、long(J)、short(S)、boolean(Z)、void(V)、Object(L),数组每一个维度前置一个"["来描述,如方法String indexOf(char[] cs,int so,char[] tg,int from,int to)描述符为"([CI[CII)Ljava/lang/String"
attributes_count+attributes:属性表集合,用于存储一些额外信息
字段表集合中不会列出从超类或父接口中继承而来的字段,但有可能列出原本Java代码中不存在的字段,比如为了保持内部类对外部类的访问会自动添加外部类实例字段。Java中字段名称必须不一样,但字节码中两个字段的描述符不一样就是合法的
2.7 methods_count+methods(方法表集合)
方法表结构与字段表结构一致
access_flags:增加ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTEP、ACC_ABSTRACT、ACC_BRIDGE(是否由编译器产生的桥接方法),没有ACC_VOLATILE、ACC_TRANSIENT
name_index、descriptor_index:类似字段表
attributes_count+attributes:属性表集合,方法里面的Java代码经过编译成字节码指令后存放在属性表集合中一个名为“Code”的属性里面
父类方法没有在子类中重写就不会出现父类方法,但有可能出现编译器自动添加的方法,最典型的有类构造器"<cinit>"方法和实例构造器"<init>"方法。Java中重载方法除了名称相同外必须要有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,不包括返回值。Class文件特征签名范围更大一些,描述符不是完全一致的两个方法可以共存
2.8 attributes_count+attributes(属性表集合)
属性表集合不要求各个属性表有严格顺序,只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略它不认识的属性
关键属性:Code(用于方法表、表示Java代码编译成的字节码指令)、ConstantValue(用于字段表,藐视final常量)、Deprecated(用于类、方法表、字段表,表示被声明为deprecated的类、方法、字段)、Exceptions(用于方法表,表示方法抛出的异常)、EnclosingMethod(用于类文件,标识类所在的外围方法,仅适用于局部类或匿名类)...
属性表结构:attribute_name_index(u2*1)、attribute_length(u4*1)、info(u1*attribute_length)
Code属性:出现在方法表的属性集合中,但接口方法或抽象方法没有【Code属性表结构】
Exceptions属性:列举方法可能跑出的受检异常【Exceptions属性表结构】
LineNumberTable属性:描述Java源码行号与字节码行号之间的对应关系,默认会生成到Class文件,可以用-g:none或-g:lines设置是否生成,如果不生成在异常堆栈中不会显示行号,也不能按照源码行设置断点【LineNumberTable属性表结构】
LocalVariableTable属性:描述栈帧中局部变量表中变量与Java源码中定义变量之间的关系,默认生成到Class文件,可以用-g:none或-g:vars设置是否生成,如果不生成在其他人调用该方法时参数名会丢失,IDE使用arg0、arg1代替【LocalVariableTable属性表结构】
LocalVariableTypeTable属性:JDK1.5引入泛型后增加,结构与LocalVariableTable类似,只是把字段描述符的descriptor_index替换为字段的特征签名(Signature)。对非泛型类型来说描述符和特征签名所描述的信息基本一致,对泛型类型来说描述符中泛型的参数化类型会被擦除,描述符不能准确地描述泛型类型,因此出现了LocalVariableTypeTable
SourceFile属性:用于记录生成这个Class文件的源码文件名称,可以用-g:none或-g:source设置是否生成,如果不生成抛出异常时不会显示出错代码所属文件名【SourceFile属性表结构】
ConstantValue属性:通知虚拟机自动为静态变量赋值,只有static变量才可以使用这项属性。实例变量赋值在实例构造器方法<init>中,类变量赋值两种方式:类构造器方法<cinit>中或使用ConstantValue属性。目前Sun Javac编译器:如果变量被final+static修饰且为基本类型或字符串则生成ConstantValue属性进行初始化,否则在<cinit>方法中初始化【ConstantValue属性表结构】
InnerClasses属性:用于记录内部类与宿主类之间的关联,编译器会为外部类及其内部类生成InnerClasses属性【InnerClasses属性表结构】
Deprecated及Synthetic属性:Synthetic表示此字段或方法并不是由Java源码直接生成的,而是由编译器自行添加【Deprecated及Synthetic属性表结构】
StackMapTable属性:【StackMapTable属性表结构】
3.字节码指令
Java虚拟机指令 由操作码(一个字节长度、代表某种特定操作的数字)和操作数(操作码后的零至多个代表此操作所需参数)构成,由于Java虚拟机采用面向操作数栈架构,因此大部分指令只有操作码,不包含操作数
操作码助记符中有特殊字符表示为哪种数据类型服务:int-i、long-l、short-s、byte-b、char-c、float-f、double-d、reference-a
3.1 加载和存储指令
将一个局部变量加载到操作数栈:[i/l/f/d/a]load、[i/l/f/d/a]load_<n>,n表示操作数个数【iload_0==iload】
将一个数值从操作数栈存储到局部变量表:[i/l/f/d/a]store、[i/l/f/d/a]store_<n>
将一个常量加载到操作数栈:[b/s]ipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
3.2 运算指令
Java虚拟机没有直接支持byte、short、char、boolean类型的算术指令,对于这类运算使用int类型指令代替
加法:[i/l/f/d]add
减法:[i/l/f/d]sub
乘法:[i/l/f/d]mul
除法:[i/l/f/d]div
求余:[i/l/f/d]rem
取反:[i/l/f/d]neg
位移:[i/l]shl、[i/l]shr、[i/l]ushr
按位或:[i/l]or
按位与:[i/l]and
按位异或:[i/l]xor
局部变量自增:iinc
比较:[d/f]cmpg、[d/f]cmpl、lcmp
最接近数舍入模式:IEEE 754默认,非精确的结果必须舍入到可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,优先选择最低有效位为零的。Java虚拟机在进行浮点数运算时采用该模式
向零舍入模式:在目标数值类型中选择一个最接近但是不大于原值的数字来作为最精确的舍入结果,所有小数部分的有效字节都会被丢掉。Java虚拟机在把浮点型转整型时采用该模式
3.3 类型转换指令
Java虚拟机直接支持(无需转换指令)数值类型的宽化,如int到long、float、double,long到float、double,float到double,转换指令包括:i2[b/c/s]、[l/f/d]2i、[f/d]2l、d2f,窄化转换可能产生不同的正负号、丢失精度、上限溢出、下限溢出等
3.4 对象创建与访问指令
创建类实例:new
创建数组:newarray、anewarray、multitianewarray
访问类字段和实例字段:getfield、putfield、getstatic、putstatic
数组元素加载到操作数栈:[b/c/s/i/l/f/d/a]aload
操作数栈值存储到数组:[b/c/s/i/l/f/d/a]astore
数组长度:arraylength
检查类实例类型:instanceof、checkcast
3.5 操作数栈管理指令
操作数栈顶一个或两个元素出栈:pop、pop2
复制栈顶一个或两个元素并将复制结果压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
栈顶两个值互换:swap
3.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
3.7 方法调用和返回指令
invokevirtual:调用对象实例方法,根据对象的实际类型进行分派(虚方法分派),Java最常见
invokeinterface:调用接口方法,运行时搜索实现接口的类实例,找到合适方法调用
invokespecial:调用一些需要特殊处理的实例方法(包括实例初始化方法、私有方法、父类方法)
invokestatic:调用static方法
invokedynamic:运行时动态解析出调用点限定符所引用的方法
3.8 异常处理指令
Java程序显式抛出异常才操作由athrow指令完成,Java虚拟机处理异常不用指令而是采用异常表完成
3.9 同步指令
Java虚拟机支持方法级同步和方法内部一段指令序列同步,这两种同步结构都是使用管程(Monitor)来支持的
方法级同步是隐式的,无需通过字节码指令来控制,虚拟机通过方法表结构中的ACC_SYNCHRONIZED访问标志得知方法是否为同步方法。当方法调用时,调用指令检查访问标志,如果设置则执行线程要求先成功持有管程,然后才能执行方法,最后方法执行完成释放管程,方法抛出异常并且方法内部无法处理管程在异常抛出同步方法之外时自动释放
同步一段指令序列Java虚拟机采用monitorenter、monitorexit指令支持
0 0
原创粉丝点击