class文件结构

什么是.class文件?

.class文件是用于给jvm加载类的文件,其存储的形式是以8字节为单位的二进制文件流.在.class文件中,各个数据项目严格按照顺序排在一起,中间没有分隔符.

.class文件使用一种类似struct的结构体,里面只有两种数据类型,无符号数与表.

无符号数按照字节多少分为u1,u2,u4,u8,对应1,2,4,8个字节.

表是由多个无符号数和其他表复合起来的类型,通常以_info进行结尾.

class文件就是由这样的复合结构组成,整个class可以作为一整张表来看待,其组成如下

class文件字段含义

这一节记录上面的图中,class文件结构中各个主要数据项的含义

  1. magic.一个魔数,确定这个文件是否是被虚拟机接收的.class文件
  2. minor_version.指定class文件的次版本号
  3. major_version.指定class文件的主版本号
  4. constant_pool_count.常量池入口,因为常量池的容量通常是不确定的,所以要放置一项u2表示容量计数值,
  5. constant_pool.遇到的第一个表结构,表示常量池,里面主要放置字面量,即文本字符串,声明为final的常量值,以及符号引用,如类与接口的全限定名,字符的名称与描述符,方法的名称与描述符等.每一个常量都是一个表,其结构都是相似的,开始有一个u1的标志位来说明类型,后面接着对应字节长度的常量数据或者是对类型的索引数据(u2).
  6. access_flags.代表访问标志,比如是类还是接口,有没有被声明为public,final等等
  7. this_class,super_class.都是u2类型的数据,类索引用于确定这个类的全限定名,super_class用于确定父类的全限定名
  8. interfaces_count与interfaces用于指定这个类实现的接口.这两个字段与上面的两个字段共同确定了一个类的继承关系.
  9. fields_count与fields共同组成了这个类的字段表的说明.字段包括了类级的变量与实例变量,其中,field_info表中记录了这个字段的元信息,比如作用域,是否static,可变性,并发可见性,字段的数据类型,名称等等.其中,各种属性有没有被声明,可以用标志位来表示,但是字段名称,数据类型等, 则是封装在attribute_info中.
  10. 后面的4个属性与fields基本相似,用于描述类的方法与属性.

字节码指令

JVM的指令组成如下:一个单个字节长度的操作码(Opcode),紧跟其后的0到多个代表此操作所需参数,称为操作数.对于学过汇编语言的同学,JVM的指令系统与汇编语言非常相似,不过其是面向操作数栈的,而汇编面向寄存器,所以在JVM指令中,通常看不到太多操作数,而是一个单独的JVM指令.JVM对指令的解释流程基本如下

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

数据类型

在指令集中,大多数指令包含了操作对应的数据类型,可能在虚拟机内部他们的实现相同,但是class文件中有各自独立的操作码.对于大部分与数据类型相关的字节码指令来说,i对应int类型,l对应long...a对应reference为引用类型.

一般来说,大多数的指令都没有支持byte,short,char和boolean类型,因为编译器在编译或者运行的时候,将这些类型转为相应的int类型数据.所以基于这些类型的指令都是使用int类型作为运算类型的.

加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈中来回传输.

包括:

  • 将局部变量加载到操作数栈:iload,iload_<n>,aload,aload_<n> ...
  • 将一个数值从操作数栈存储到局部变量表:isore,istore_<n>,astore,astore_<n>
  • 将一个常量加载到操作数栈:bipush,sipush,ldc,ldc_w,...
  • 扩充局部变量表的访问索引指令:wide

其中,里面的<n>代表的是一组指令,其都是某个带有一个操作数的通用指令,他们隐藏了显示的操作数.

运算指令

运算指令用于对两个操作数栈上的值进行某种特地的运算.并把结果重新存入到操作数的栈顶.大体上可以分为对整数进行运算和对浮点数进行运算.与上面所说的一样,byte,short等也是基于int来进行运算的.

  • 加法指令:xadd,其中x表示4中,i,l,f,d分别为int,long,float,double,下同
  • 减法指令:xsub
  • 乘法指令:xmul
  • 除法指令:xdiv
  • 取余指令:xrem
  • 取反指令:xneg
  • 位移指令:xshl,xshr,xushr,这里的x只有int与long,
  • 按位或:ior,lor
  • 按位与:iand,land
  • 按位异或:ixor,lxor
  • 局部变量自增:iinc
  • 比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

类型转换指令

类型转换可以将两种不同的数值类型进行相互转换,这些转换的操作一般用来实现我们的代码中显式的类型转换操作.JVM直接支持从小范围类型转向大范围类型的安全转换

  • int -> long,float,double
  • long -> float,double
  • float -> double

如果我们要从大范围转到小范围,就要显式调用转换指令了.包括

  • i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f.

这些指令都非常明显,第一个字符为转换前的类型,2表示to,最后的字符表示转换后的类型.对于窄化类型的转换,有可能会发生精度丢失,溢出.但是JVM不会对这些问题作出异常或处理.

对象创建与访问

  • 创建类实例:new
  • 创建数组:newarray,anewarray,multinewarray
  • 访问类字段和实例字段:getfield,putfield,getstaic,putstatic
  • 把一个数组元素加载到操作数栈:baload,caload,saload,iaload,laload,daload,daload,aaload
  • 将一个操作数栈的值存到数组中:bastore,castore,sastore,iastore,fastore,dastore,aastore.
  • 取数组长度:arraylength
  • 检查类实例的类型:instanceof checkcast

操作数栈管理指令

  • 将操作数栈的栈顶的一个或者两个元素出栈:pop,pop2
  • 赋值栈顶的数值,并重新压栈:dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2
  • 将栈顶的两个元素位置互换:swap

控制转移指令

控制转移指令可以让JVM有条件或者无条件地从指定的位置跳转.本质上就是修改PC的值

  • 条件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,...
  • 复合条件分支:tableswitch,lookupswitch
  • 无条件分支:goto...

方法调用与返回

invoke....

异常处理

在JVM中抛出异常都会使用athrow来实现.而处理异常则使用异常表来实现

同步指令

JVM可以支持方法级的同步和方法内部一段指令序列的同步,都是通过管程来实现的.通过方法表中的ACC_SYNCHRONIZED访问标志得知是否为同步方法,如果是同步的,那么就要求方法先成功持有管程,执行完毕后再释放.

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×