标签:
1. Java一次编写,到处运行的基石:
Java编译产生的是字节码(bytecode),sun公司和其他虚拟机提供商发布各个平台上的虚拟机,这些虚拟机可以载入和执行这些与平台无关的字节码。
2. Class类文件结构:
Class文件是一组以字节为基础单位的二进制字节流,各个数据项目严格按照顺序紧凑的排列在class文件之中,中间没有任何分隔符。
Class文件使用无符号数和表来存储所有的数据。无符号数属于基本数据类型,用u1、u2、u4、u8来代表1个字节、2个字节、4个字节和8个字节的无符号数;表是由多个无符号数和其他表构成的符合数据类型。
整个class文件本质上就是一张表,它的数据项如下:
Class文件的格式
无论是无符号数还是表,当要描述同一类型但数量不定的多个数据的时候,经常会使用一个前置的容量计数器加若干连续的数据项的形式,称为一个集合。
这里需要重点声明一下的是,Class文件没有任何的分隔符,所以上图中的所有数据项,无论是顺序还是数量,甚至数据存储的字节序,都被严格限定,不允许改变。
Class文件中的常量池:常量池是class文件的资源仓库,存放两类常量:字面量和符号引用。字面量包括文本字符串、声明为final的常量;符号引用包括:①类和接口的全限定名、②字段的名称和描述符、③方法的名称和描述符。常量池的每一项常量都是一个表,一共14中表结构,例如:
Constant_Class_info型常量的结构
常量池并没有固定的结构,它只是一个资源仓库,想查找它里面的内容只需提供索引值即可。
这里解释一下为什么需要符号引用,而不是直接引用符号引用引用的那个东西。因为常量池是一个资源仓库,它的本质目的是存储代码中的类、字段、方法、常量之类的信息,但是这些信息有的特别复杂,比如方法,它既有方法名,又有方法参数,还有返回类型(后面我们知道这叫方法描述符),你要说明一个方法,这些信息缺一不可。除此以外,有的信息我们会多处用到,比如当前类信息。在这种情况下,我们让常量池的一些结构尽可能的细小,而对于复杂的结构,它们包含了对这些细小的基本结构的引用,这便是符号引用的目的。这样就避免了重复存储。
Class文件中的类索引、父类索引、接口索引集合:类索引和父类索引都是一个u2类型的数据,它们都有且只有一个(因为java不允许多继承),他们的值都是指向常量池中相应项的一个索引值。接口索引集合入口的第一项是接口索引的数量,后面是一系列u2类型的索引指向常量池中的相应值的索引。
字段表集合:字段表用于描述类或接口中声明的变量。字段表的结构如下:
字段表结构
这些属性按access_flags、name_index、descriptor_index、attributes_count、attributes的顺序排列。
⑴access_flags存储字段修饰符,跟类中的access_flags非常相似,都是一个u2的数据类型,其中可以设置的标志位和含义如下:
字段访问标志
⑵name_index存储对常量池的索引,代表字段的简单名称。
⑶descriptor_index存储对常量池的索引,存储字段的描述符。关于简单名称、描述符、全限定名,这里做一个介绍如下:
①简单名称就是没有类型和参数修饰的方法或字段的名称,如inc()方法和m字段的简单名称就是inc和m;
②全限定名很好理解,类的全限定名就是包1.包2….类名,在class文件中不用.的ascii码而是用的/作分隔符,也就是包1/包2/…/类名;
③描述符比较复杂,描述符的作用是描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值,基本数据类型及void用一个大写字符来表示,对象类型用L加对象的全限定名来表示,具体如下:
描述符标识字符含义
对于数组类型,每一个维度用一个前置的[字符来描述,例如java.lang.String[][]类型的二维数组会被记录为[[Ljava/lang/String;,int[]型数组会被记录为[I。用描述符描述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序存放在()之内,例如void inc()描述符为()V,java.lang.String toString()描述符为()Ljava/lang/String;,intindexOf(char[] source, int sourceOffset, int sourceCount, char[] target, inttargetOffset, int targetCount, int fromIndex)的描述符为([CII[CIII)I。
⑷在descriptor_index之后跟随着一个属性表集合用来存储一些额外的信息。
字段表集合不会列出从超类或者父接口中继承而来的字段,但有可能列出原Java代码中不存在的字段,如内部类为了保持对外部类的访问性会自动添加指向外部类实例的字段。
方法表集合:方法表的结构和字段表类似,如下:
方法表结构
这些数据项目的含义也非常类似,仅在访问标志和属性表集合的可选项中有所差别。
方法访问标志
方法的定义可以通过访问标志、名称索引、描述符索引表达清楚,那么方法的代码那里去了呢?方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表中一个名为Code的属性里面。如果父类的方法没有被子类覆盖,则方法表集合中不会出现父类的方法信息。但同样的,有可能出现编译器自动添加的方法,如类构造器<cinit>方法和实例构造器<init>方法。
属性表集合:属性表用来描述场景专有的信息。属性表不需要具有严格的顺序,对于每个属性,它的名称需要从常量池中引用一个Constant_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要用一个u4类型的数据去说明属性值所占用的位数即可,属性表结构如下:
属性表结构:
典型的属性,如Code,其结构如下:
3. 字节码指令:
Java虚拟机的指令由一字节长度的操作码加零至多个操作数组成。
标签:
原文地址:http://blog.csdn.net/nlznlz/article/details/51082245