深入理解java虚拟机---类文件结构


1.Class类文件的结构


Class文件格式采用一张类似于C语言结构体的伪结构来存储数据,这种伪数据结构中只有两种数据类型:无符号数和表:
无符号
数属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数。
是由多个无符号数或者其他表作为数据项构成的复合数据类型。整个Class文件本质上就是一张表。如下图所示

深入理解java虚拟机---类文件结构_第1张图片

1.1魔数和Class文件的版本


每个Class问价的头4个字节称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件
紧接着魔数的四个字节1存储的是Class文件的版本号:第5和第6字节是次版本号,第7和第8是主版本号。


1.2常量池


在魔数和版本后的是常量池的入口,常量池可以理解为Class文件之中的资源仓库。
常量池的入口中有一个u2类型的数据,代表常量池的容量计算值(constant_pool_count)。这个容量的计算值是从1开始计算的。
常量池中主要存放两大类常量(字面量符号引用)字面量主要指的是如文本字符串,申明为final的常量值等。而符号引用主要包括以下三个部分:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

常量池中每一项常量都是一个表,到jdk1.7时有14项类型,如下图所示:

这14中类的常量其第一位是u1类型的标志位(tag),表示是哪种类型的常量。继而是各个类型其对应的结构及其表示,具体的每种类型结构可从下图了解:


1.3 访问标志


在常量池之后随后的两个字节表示的是访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息:如这个class是类还是接口;是否定义为public类型...具体见下图:

深入理解java虚拟机---类文件结构_第2张图片

1.4 类索引,父类索引与接口索引集合


类索引和父类索引都是一个u2类型的数据,分别表示当前类和其父类的而接口是一组u2类型的数据的集合,接口索引集合是一组u2类型的数据的集合,class文件由这三项数据来确定这个类的继承关系。


1.5 字段表集合


字段表用于描述接口或者类中声明的变量。但不包括方法内部声明的局部变量,其主要结构如下图所示。

深入理解java虚拟机---类文件结构_第3张图片

其中access_flags表示的是字段的相关修饰字段,如是否为public,private,protected,static,final,volatile,transient,enum或编译器自动产生的。
其后是name_indexdescriptor_index两个索引值,它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。
方法和字段的描述符的规则为:基本数据类型(byte,char,double,float,int,long,void,boolean)等都用大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。举例来说:int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex)的描述符为"([CII[CIII)I"。


1.6 方法表集合


Class文件格式对于方法的描述和对于字段的描述方式基本一致,方法表的结构和一致,其结构图可以见方法表。对于方法表中access_flags的表述由于方法中没有(transient,volatile等描述,相应的添加了如synchonized,native,strictfp和abstract等属性)。

在方法集中,可能存在编译器自动添加的方法,最典型的就是类构造器方法""和实例构造器方法"" 方法。
在java语言中,要重载(overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合。


1.7 属性表集合


方法表,字段表中都携带自己的属性表集合,以用于某些场景专有的信息。虚拟机中的属性有下图所示21中:

深入理解java虚拟机---类文件结构_第4张图片
深入理解java虚拟机---类文件结构_第5张图片

1.7.1 code属性


java程序代码块中的代码经过javac编译器编译处理后,最终变为字节码存储在code属性内。
code属性表结构中主要有一下几项:attribute_name_index指向一个恒定为"Code"的CONSTANT_Utf8_info型常量的索引。表示属性名称
max_stack代表操作数栈深度的最大值。max_locals代表局部变量表所需的存储空间.在这里,max_locals的单位是slot。code_length最大值为65535,一般不会超过这个值,但是如果是编译复杂的jsp文件时,可能超过这个值。
在javac对实例方法的实现时将this作为普通参数调用,在虚拟机调用实例方法时自动传入了this这个参数。

在code属性中还有的是异常表。异常表中有四个部分:(start_pc , end_pc , catch_type , handler_pc)在start_pc到end_pc之间出现了类型为catch_type或者其子集的异常时则转到handler_pc中进行处理。当catch_type=0,表明任何异常都要转到hander_pc中执行。


1.7.2 Exceptions属性

Exceptions属性列出可能抛出的受检查的异常


1.7.3 LineNumber属性

LineNumber属性用于描述java源码行号与字节码行号之间的对应关系(可以在javac中用-g:none或-g:lines取消生成)


1.7.4 LocalVariableTable属性

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系(可以在javac中用-g:none或-g:vars取消生成)


1.7.5 SourceFile属性

SourceFile属性可以用于记录生成这个Class文件的源码文件名称(可以在javac中用-g:none或-g:source取消生成)


1.7.6 ConstantValue属性

ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。
对于类变量和实例变量赋值的方式不同,实例变量的赋值是在""方法中进行的;而对于类变量的实现有两种方式,一种是通过""方法,另一种方式是通过constantValue方式。


1.7.7 InnerClass属性

InnerClass属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那么编译器将会为它和它所包含的内部类生产InnerClasses属性
InnerClass属性结构和inner_class_info属性结构如下图:


1.7.8 Deprecated和Syntheic属性

Deprecated属性表示作者已不推荐使用,Syntheic表示此字段或者方法是由编译器自动生成


1.7.9 StackMapTable属性

这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。


1.7.10 Signature属性

任何类,接口,初始化方法或成员的泛型类型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。它的作用是为了处理java对于泛型的使用时使用伪泛型中如运行期做反射时无法获得反射信息等问题而存在的。


1.7.11 BootstrapMethods属性

BootstrapMethods属性用于保存invokedynamic指令引用的引导方法限定符。


2 字节码指令

2.1 加载和存储命令

  • 将一个局部变量记载到操作数栈: iload , iload_ , lload , lload_, fload , fload_ , dload , dload_ , aload , aload_
  • 将一个数值从操作数栈存储到局部变量表:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstore_,astore,astore_
  • 将一个常量加载到操作数栈:bipush , sipush , ldc , ldc_w , ldc2_w , aconst_null , iconst_m1 , iconst_ , lconst_,fconst_ , dconst_
  • 扩充局部变量表的访问索引指令:wide

其中如iload_等以n结尾的n为iload_0 , iload_1,iload_2等。


2.2 运算指令

  • 加载指令: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
  • 按位异或指令:iinc
  • 比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

2.3 类型转换指令

java直接支持基本类型的向宽化类型转型,对于java基本类型的范围排序为(charchar和byte类型转换时都需要显示转型


2.4 对象创建和访问指令

  • 创建类实例指令:new
  • 创建数组指令:newarray , anewarray , multianewarray
  • 访问类字段(static)和实例字段(非static):getfiled,putfiled,getstatic,putstatic
  • 把一个数组元素加载到操作数栈的指令:baload,caload,saload,iaload,laload,faload,daload,aaload
  • 将一个操作数栈的值存储到数组元素:bastore,castore,sastore,iastore,fastore,dastore,aastore
  • 取数组长度指令:arraylength
  • 检查类实例类型指令:instanceof,checkcast

2.5 操作数栈管理指令

  • 将操作数栈的栈顶一个或两个元素出栈:pop,pop2
  • 复制栈顶一个或两个数值并将复制或双份的复制值重新压入栈顶:dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2
  • 将栈顶端的两个数值互换:swap

2.6 控制转移指令

  • 条件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,if_icmpeq,if_icmpne,if_icmplt,if_icmple,if_icmpge,if_acmpeq,if_acmpne
  • 复合条件分支:tableswitch,lookupswitch
  • 无条件分支:goto,goto_w,jsr,jsr_w,ret

对与char,boolean,byte,short等类型的比较会转换为int类型,而对于long,float和double类型则先调用比较运算指令(dcmpg,dcmpl,fcmpg)比较后返回int类型的值再用int类型判断


2.7 方法调用和返回指令

  • invokevirtual调用对象的实例方法
  • invokeinterface用于调用接口方法
  • invokestatic用于调用类方法
  • invokedynamic用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法

2.8 异常处理指令

java虚拟机中,处理异常不是由字节码来实现,而是用异常表来实现(code属性中的exception table)


2.9 同步指令

同步指令集monitorenter和monitorexit,当方法调用时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了执行线程要求成功持有管程,在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果同一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个同步方法所持有的管程将在异常抛出时自动释放。


参考

<<深入理解java虚拟机>>

你可能感兴趣的:(深入理解java虚拟机学习笔记)