JVM-class文件结构解析

3/8/2017来源:ASP.NET技巧人气:417

解析class文件的结构 魔数 + class文件次版本号 + class文件主版本号 常量池 访问标志 类索引,父类索引,接口索引 字段表集合 方法表集合 属性表集合

class文件是一组以8位字节为基础单位的二进制流,紧密排列没有分隔符。 class文件数据结构,包括两种数据类型:无符号数无符号数:属于基本的数据类型,u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节。 :表是由多个无符号数或其他表作为数据项构成的符合数据类型,所有表都习惯性地以“_info”结尾。 这里写图片描述

1)魔数 + class文件次版本号 + class文件主版本号

魔数:前四个字节,为确定的0xCA FE BA BE。 class文件此版本号+class文件主版本号:第5,6与第7,8字节 这里写图片描述

2)常量池

第一个字节为常量池容量的计数值,计数从1开始,将第0项常量空出来是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的意思。例:该字节为0x0016,转化为十进制为22,这就代表常量池有21项常量,索引值为1~21。

常量池之中主要存放两大类常量:字面量符号引用字面量:比较接近于java语言的常量概念,如文本字符串,被声明为final的常量值。 符号引用:属于编译原理方面的概念,包括:类和接口的全限定名 和 字段的名称和描述符 和 方法的名称和描述符。

常量池中每一项常量都是一个表,共有11种不同的常量数据结构表: 这里写图片描述 这里写图片描述 这里写图片描述

3)访问标志

常量池结束之后,紧接着的2个字节代表访问标志,这标志用于识别一些类或接口的层次的访问信息,包括:这个Class是类还是接口;是否定义为public;是否为abstract类型;是否为final等等。 这里写图片描述 总共两个字节16位可以使用,当前之定义了其中的8个

4)类索引,父类索引,接口索引

类索引与父类索引:用于确定这个类与父类的全限定名。各用一个u2类型常量池引用表示。 接口索引:入口第一项为u2类型的计数器,表示索引表的容量。若没有接口则为0。若有则在后面通过相应数量的u2类型常量池引用表示。

5)字段表集合

字段表集合:用于描述接口或类中声明的变量,包括类级变量与实例级变量,并不包含局部变量。 字段表结构:标志位,跟随其后的是两项索引值:name_index与descriptor_index。他们都是对常量池的引用,分别代表着字段的简单名称及字段和方法的描述符。(这里表示字段的类型,字段就是变量)。attributes_count,attribute_info表示属性,后边有介绍。 这里写图片描述

标志位:跟类的访问标志相似。 这里写图片描述

描述符:是用来描述字段的数据类型,方法的参数列表和返回值。基本类型(byte,char,double,float,int,long,short,boolean)及void都是用一个大学字符来表示,而对象类型用字符L加对象的全限定名来表示。 这里写图片描述 对于数组如:java.lang.String[][] 表示为:”[[Ljava/lang/String;” 用描述符描述方法时:先参数后返回值:”int f(int i, char c, String[] ss)” 表示为:”(IC[Ljava/lang/String)I”

6)方法表集合

方法表结构与字段表结构一样,但各项含义有所不同。 方法访问标志也与字段访问标志基本一致。 方法名有了,但方法中的代码去哪了?方法里的代码经过编译器编译成字节码指令之后,存放在方法属性表集合中一个名为“Code”的属性里,属性表作为Class文件格式中最具扩展行的一种数据项目,将在后边详解。

如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。有可能会出现来自编译器自动添加的类构造器《clinit》方法与《init》方法,这两个将在第十章说明。

7)属性表集合

属性表在前面的讲解中出现过多次,在Class文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。 这里写图片描述

还有时间,接下来简单学习一下各个属性的含义:

① Code

这里写图片描述

attribute_name_index:是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定位“COde”,他代表了该属性的属性名称 attribute_length:指示了属性值的长度,属性值长度为属性表长度减去attribute_name_index与attribute_length的长度(即减6)。 max_stack:代表了操作数栈深度的最大值。虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。 max_locals:局部变量表所需的存储空间。单位为Slot。byte,char,float,int,short,boolean,reference,returnAddress占1个Slot。double,long占2个Slot。方法参数(包括this),显示异常处理器的参数,内部局部变量都用该表存放。 code_length与code:用来存储java源程序编译后生成的字节码指令。 exception_table_length与exception_table:用来执行遇到异常时的代码跳转处理。如下图,一个try,catch,finally组合中的几个跳转。 这里写图片描述

② Exception属性

表示方法可能抛出的异常,也就是方法描述时在throws关键字后面列举的异常。

③ LineNumberTable属性

java源码行号与字节码行号之间的对应关系。可有可无,会默认生成。没有这项属性的后果是抛出异常时将不会显示出错行号。

④ LocalVariableTable属性

局部变量们的名称,类型与生存周期,及在栈帧局部变量表中所占空间的位置。

问题:为什么没有变量的初始化值?

这里写图片描述 这里写图片描述

⑤ SourceFIle属性

记录生成这个CLass文件的源码文件的名称。

⑥ ConstantValue属性

作用是通知虚拟机自动为静态变量赋值,只有static关键字修饰的变量才可以使用这项属性。 对于非static类型的变量的赋值是在实例构造器《init》方法中进行的。而对于非static类型的变量的赋值是在类构造器《clinit》方法中进行的,或者使用ConstantValue属性来赋值。目前编译器的选择是:如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或java.lang.Sring的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被fianl修饰,或者并非基本类型及字符串,则选择在《clinit》方法中就行初始化。

⑦ InnerClasses属性

用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那么编译器将会为 它及其所包含的内部类 生成InnerClasses属性。

问题: innerClass的代码呢?

这里写图片描述 这里写图片描述