jvm随笔7-class类文件的结构

class文件是一组以8字节为基础单位的2进制流,其储存内容全为必要数据,没有空隙。若数据大于8字节,按照类似大端的方式,以高低低高的原则进行数据存放。
class文件中使用一种类c的数据结构,此结构只有两种数据类型,无符号数与表。

  • 无符号数有u1、u2、u4、u8四种类型分别代表1、2、4、8字节的无符号数。
  • 表由无符号数和其他表作为数据项的复合数据类型,表习惯以“_info”结尾。class文件的本质就是一张表

class文件排布格式:

类型 名称 数量
u4 magic(魔数) 1
u2 minor_version(次版本号) 1
u2 major_version(主版本号) 1
u2 constant_pool_count(常量池容量) 1
cp_info constant_pool(常量池) constant_pool_coun-1
u2 access_flags() 1
u2 this_class() 1
u2 super_class() 1
u2 interfaces_count() 1
u2 interfaces() interfaces_count-1
u2 fields_count() 1
field_info fields() fields_count-1
u2 methods_count() 1
method_info methods() methods_count-1
u2 attributes_count() 1
attribute_info attributes() attributes_count-1

1.魔数

class文件开头的四个字节是一个四字节数,四个字节为8个二进制数,0xCAFEBABE,用于确定这个文件是否为能被虚拟机接收的class文件,使用魔数而不用拓展名是出于安全考虑。

2.次版本号与主版本号

接下来四个字节为次版本号与主版本号,先次后主,每个两字节。次版本号代表主版本的不同版本,主版本号从45(JDK1.1)开始,后每次加一。JDK为高版本向下兼容,虚拟机会根据主版本号拒绝执行超过其版本的class文件。

3.常量池

3.1 常量池容量:

常量池作为class文件的资源仓库,且资源量不固定,所以接下来的两个字节表示常量池的大小,表示常量池的资源个数。
而常量池资源数量计数比较特殊,从1开始而不是0,若常量池容量为十进制22,则表示常量个数为21个(索引为1-21),把0空出来是为了用来表示特殊情况(不引用任何一个常量池项目)。

3.2 常量池

常量描述:
常量次主要存放两类常量:字面量和符号引用。

  • 字面量:类似常量的字面意思,如文本符号串,声明了final的常量值等。

  • 符号引用

    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

Java不像c有连接的步骤,只会在虚拟机加载class文件时进行动态连接,所以class不保存字段与方法的内存布局(因为暂时用不到),只会在虚拟机运行时从常量池中获得符号引用,再在类创建或运行时翻译到具体内存地址中。

在Jdk1.7之前有11种结构不同的表结构数据,JDK1.7之后又引入了三种,此处不详细阐述各种项目类型。

项目结构:
进入常量池后,首先是一个1字节的数据,它是常量的标志,表示常量是那种类型,由这个标记确定后面的数据是那种表,紧接着就是这个常量的具体内容(内容排布由标志确定的表结构来确定),在这个常量内容结束以后紧接着就是下一个常量的标志,如此一直到所有常量完毕。(具体有哪些表结构及标志对应关系可查看《深入理解jvm》p169-p172)。

4.访问标志

两个字节大小,一共16位,其中八个位用于使用,标识其是否定义为public类型,是否定义为abstract类型,若是类,是否声明为final,还用于标识其是不是接口,由用户代码产生,是不是注解,是不是枚举。共八种,有这两个字节对应的八个位来标识。

5.类索引,父类索引,接口索引集合

索引都是一个u2类型的数据,通过这个数据可以找到定义在常量池中的全限定名字符串数据,类索引表示当前类的全限定名,由于Java是单继承多实现的,只有一个父类索引,而接口索引是一个集合。
这些索引在class中的排布是先类索引,再父类索引,然后有2字节长度的无符号数表明有多少个接口索引,然后就是对于数量个接口索引,按照写的时候从左到右的顺序排布

6.字段表集合

用于描述接口或类中变量(不包括方法中的局部变量)

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count-1

变量构造:各种修饰符 数据类型 字段名称 额外信息(属性表集合)

  • 字段修饰符在属性access_flags中长度为2字节,类似访问标志,每个位代替一种修饰符是不是被使用了,这里共有九种描述符来修饰变量(public,private,protected,static,final,volatile,transient,synthetic,enum),所以使用两个字节中的九位。
  • 数据类型(可能是类名)与字段名称(自定义)都是存在多种变化的,所以都是一个u2数据(name_index和descriptor_index),均代表一个索引,指向常量池中的其对应的简单名称(字段名称),描述符(数据类型)。
  • 额外信息则使用属性表集合储存(比如变量使用了final修饰,则需要属性表集合来存储变量的值);结构上先是一个u2的数据attributes_count代表属性表集合的长度,然后就是若干个属性表结构代表这些额外信息。

7.方法表集合

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count-1

方法构造:各种修饰符 返回数据类型 方法名(参数) 额外信息(属性表集合)

结构上与字段表集合十分类似:

  • 修饰符在属性access_flags中长度为2字节,类似访问标志,每个位代替一种修饰符是不是被使用了,这里共有12种描述符来修饰变量(public,private,protected,static,final,transient,synchronized,bridge,varargs,native,abstract,strictfp,synthetic),所以使用两个字节中的12位。
  • 数据类型(返回值加参数,可能是类名)与方法名称(自定义)都是存在多种变化的,所以都是一个u2数据(name_index和descriptor_index),均代表一个索引,指向常量池中的其对应的简单名称(字段名称),描述符(数据类型)。
  • 额外信息则使用属性表集合储存(比如方法的实体,就存在属性表中);结构上先是一个u2的数据attributes_count代表属性表集合的长度,然后就是若干个属性表结构代表这些额外信息。

关于字段表与方法表中的全限定名,简单名称,描述符

  • 全限定名:类或者接口的完整路径加名字(java/lang/String),为了使得全限定名连续存在时不混淆,一般全限定名都以;结尾用于区分

  • 简单名称:用户为变量或者方法定义的名字

  • 描述符:对应基本数据类型(byte,char,double,float,int,long,short,boolean)均使用一个大写字母来描述B,C,D,F,I,J,S,Z,用V表示void,L+全限定名表示对象(Ljava/lang/String),在表述符前面加[表示数组(int[]-->[I,int[][]-->[[I

    • 对于字段的描述符,只需要表述变量的数据类型,使用上述表述方式即可
    • 对于方法的描述符,要描述参数及返回值,描述方式为:(参数按照顺序逐个描述)返回值描述符
      举例:void index(int i1,int[] i2,String[] i3,int i4,int i5,Char[] i5){}
      表示为:(I[I[Ljava/lang/StringII[C)V

属性表集合

前面已经出现数次,对应像字段使用final时储存信息,储存方法表编译成的字节码指令外还有很多其他的用处,包括异常,内部类列表,作为内部类时存储外部类等很多额外信息。

猜你喜欢

转载自blog.csdn.net/maniacxx/article/details/87863609
今日推荐