标签:cep 字段表 文件中 通知 包括 语句 复杂 最大值 except
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反之类和接口并不一定定义在文件里(比如类和接口也可以通过类加载器直接生成)。
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有任何分隔符。Class文件的结构只有两种数据类型:无符号数和表。
下面是Class文件的格式:
每个Class文件的头四个字节称为魔数,它的唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件。class文件的魔数值为CA FE BA BE。
紧挨着魔数后面的四个字节存储的是Class文件的版本号:第五和第六是次版本号(minor_version),第七和第八是主版本号(major_version),以下面的类为例:
public class TestClass{
private String m;
public String test() {
return m + 1;
}
}
生成的Java Class文件结构为:
紧接着主版本号之后是常量池容量计数值(constant_pool_count),由于常量池中常量的数量不是固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count),这个常量技术值是从1开始而不是从0开始的。上图中常量池容量为0x0016,即十进制中的22,这就表示常量池中有21项常量。
然后就是常量池(constant_pool),常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时还是在Class文件中第一个出现的表类型数据项目。
常量池中主要存放两大类常量:字面量和符号引用。
Java代码在进行javac编译时没有连接的步骤,而是在虚拟机加载Class文件的时候进行动态连接。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
个人理解符号引用的作用就是在编译时记录下文件的类、字段和方法,在JVM运行时能在需要的时候获取相应信息进行加载。
在常量池中会有一部分自动生成的常量(这些常量没有在Java代码里面直接出现过),但这些常量会被字段表、方法表、属性表引用,用来描述一些不方便使用“固定字节”表达的内容。(比如:方法的返回值?有几个参数?参数类型?等)
在常量池结束之后,有两个字节代表访问标志(access_flags),这个标志用于识别一些类和接口层次的访问信息,包括这个Class是类还是接口,是否为public类型,是否定义为abstract类型,如果是类的话,是否声明为final类型等。
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(包括了interfaces_count及interfaces)是一组u2类型的数据的集合,Class文件中由这个三个数据来确定类的继承关系。它们按顺序排在访问标志后面。
类索引用于确定这个类的全限定名,
父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继续,所以父类索引只有一个(除了java.lang.Object外所有的Java类都有父类,除了它所有的父类索引都不为零)。
接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按照Implements语句后的接口顺序从左到右排列在索引集合中。
字段表(field_info)用于描述接口或类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。可以包括的信息有:字段的作用域、是实例变量还是类变量(static 修饰符)、可变性(final)、并发可见性(volatile修饰)、是否可被序列化(transient修饰符)、字段数据类型、字段名称。上述的这些信息,各个修饰符都是布尔值。
字段叫什么名字、字段被定义为什么类型,这些是无法固定的,只能引用常量池中的常量来描述。
字段表集合中不会列出从超类或者父接口中继承而来的字段,但可能列出Java代码中不存在的字段,如内部类中保持对外部类的访问性,会自动添加指向外部类实例的字段;
方法里的Java代码经过编译器编译成字节码指令后,存放在属性表集合中一个名叫“Code”的属性里面。
如果父类方法在子类中没有被重写,那么方法表集合中不会出现来自父类的方法;同时也同样可能出现编译器自动添加的方法,典型如“类构造器< clinit >”和实例构造器"< init >"
如果要重载一个方法除了要与原方法具有相同的简单名称之外,还必须要求拥有一个与原方法不同的特征签名,即方法中各个参数在常量池中的字段符号引用集合,不包含返回值。这就是Java语言里仅仅依靠返回值不同无法对一个已有方法重载。但是在Class文件格式中即字节码层面(前面是Java代码层面),方法特征还包括方法返回值及受查异常表,因此2个不是完全一致的方法也可以合法共存与一个Class文件中。
以下是虚拟机规范定义的属性:
Code属性:Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合中,但是并非所有的方法表都必须存在这个属性,例如接口或者抽象类中的方法就不存在Code属性。其属性表结构如下:
Exceptions属性:其作用是列举出可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
LineNumberTable属性用于描述Java源代码行号与字节码行号(字节码偏移量)之间的对应关系。如果选择不生成LineNumberTable属性表,在抛出异常时堆栈中将不会显示出错的行号,也无法在调试程序的时候按照源码来设置断点。
LocalVariableTable属性表用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。非必须。如果没有生成这项属性,IDE可能会使用诸如arg0、arg1之类的占位符来替换原有的参数名称,对程序运行没有影响。
SourceFile属性用于记录这生成这个Class文件的源码文件名称。这个属性也是可选的,如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名。
作用是通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量才可以用这个属性。
目前Sun javac编译器的选择是:同时使用final和static修饰的变量且为基本数据类型或String类型使用ConstantValue属性初始化,否则使用类构造器< clinit >进行初始化。
用于记录内部类与宿主类之间的关联。
Inneclasses属性结构:
Deprecated及Synthetic属性都属性于标志类型的布尔值属性,只存在有和没有的区别,没有属性值的概念。所以在下图属性结构中attribute_length的数据值必须为0x00000000。
这是一个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
一个可选的定长属性,在JDK 1.5发布后增加的,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。这主要是因为Java的泛型采用的是擦除法实现的伪泛型,在字节码中泛型信息编译之后统统被擦除,在运行期无法将泛型类型与用户定义的普通类型同等对待。通过Signature属性,Java的反射API能够获取泛型类型。
一个复杂的变长属性,位于类文件的属性表中,用于保存invokedynamic指令引用的引导方法限定符。(最多只能有一个)
标签:cep 字段表 文件中 通知 包括 语句 复杂 最大值 except
原文地址:http://www.cnblogs.com/0427mybirthday/p/7340361.html