您好,欢迎访问三七文档
当前位置:首页 > 办公文档 > 招标投标 > 实例分析JavaClass的文件结构
实例分析JavaClass的文件结构今天把之前在Evernote中的笔记重新整理了一下,发上来供对javaclass文件结构的有兴趣的同学参考一下学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次编写,到处运行”,其实说到无关性,Java平台还有另外一个无关性那就是语言无关性,要实现语言无关性,那么Java体系中的class的文件结构或者说是字节码就显得相当重要了,其实Java从刚开始的时候就有两套规范,一个是Java语言规范,另外一个是Java虚拟机规范,Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨平台的角度去设计的。今天我们就以一个实际的例子来看看,到底Java中一个Class文件对应的字节码应该是什么样子。这篇文章将首先总体上阐述一下Class到底由哪些内容构成,然后再用一个实际的Java类入手去分析class的文件结构。在继续之前,我们首先需要明确如下几点:1)Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8个字节的数据,将按照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,因为PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。2)Class文件结构采用类似C语言的结构体来存储数据的,主要有两类数据项,无符号数和表,无符号数用来表述数字,索引引用以及字符串等,比如u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数,而表是有多个无符号数以及其它的表组成的复合结构。可能大家看到这里对无符号数和表到底是上面也不是很清楚,不过不要紧,等下面实例的时候,我会再以实例来解释。明确了上面的两点以后,我们接下来后来看看Class文件中按照严格的顺序排列的字节流都具体包含些什么数据:(上图来自TheJavaVirtualMachine)在看上图的时候,有一点我们需要注意,比如cp_info,cp_info表示常量池,上图中用constant_pool[constant_pool_count-1]的方式来表示常量池有constant_pool_count-1个常量,它这里是采用数组的表现形式,但是大家不要误以为所有的常量池的常量长度都是一样的,其实这个地方只是为了方便描述采用了数组的方式,但是这里并不像编程语言那里,一个int型的数组,每个int长度都一样。明确了这一点以后,我们在回过头来看看上图中每一项都具体代表了什么含义。1)u4magic表示魔数,并且魔数占用了4个字节,魔数到底是做什么的呢?它其实就是表示一下这个文件的类型是一个Class文件,而不是一张JPG图片,或者AVI的电影。而Class文件对应的魔数是0xCAFEBABE.2)u2minor_version表示Class文件的次版本号,并且此版本号是u2类型的无符号数表示。3)u2major_version表示Class文件的主版本号,并且主版本号是u2类型的无符号数表示。major_version和minor_version主要用来表示当前的虚拟机是否接受当前这种版本的Class文件。不同版本的Java编译器编译的Class文件对应的版本是不一样的。高版本的虚拟机支持低版本的编译器编译的Class文件结构。比如JavaSE6.0对应的虚拟机支持JavaSE5.0的编译器编译的Class文件结构,反之则不行。4)u2constant_pool_count表示常量池的数量。这里我们需要重点来说一下常量池是什么东西,请大家不要与Jvm内存模型中的运行时常量池混淆了,Class文件中常量池主要存储了字面量以及符号引用,其中字面量主要包括字符串,final常量的值或者某个属性的初始值等等,而符号引用主要存储类和接口的全限定名称,字段的名称以及描述符,方法的名称以及描述符,这里名称可能大家都容易理解,至于描述符的概念,放到下面说字段表以及方法表的时候再说。另外大家都知道Jvm的内存模型中有堆,栈,方法区,程序计数器构成,而方法区中又存在一块区域叫运行时常量池,运行时常量池中存放的东西其实也就是编译器长生的各种字面量以及符号引用,只不过运行时常量池具有动态性,它可以在运行的时候向其中增加其它的常量进去,最具代表性的就是String的intern方法。5)cp_info表示常量池,这里面就存在了上面说的各种各样的字面量和符号引用。放到常量池的中数据项在TheJavaVirtualMachineSpecifi个常量,每一种常量都是一个表,并且每种常量都用一个公共的部分tag来表示是哪种类型的常量。下面分别简单描述一下具体细节等到后面的实例中我们再细化。CONSTANT_Utf8_infotag标志位为1,UTF-8编码的字符串CONSTANT_Integer_infotag标志位为3,整形字面量CONSTANT_Float_infotag标志位为4,浮点型字面量CONSTANT_Long_infotag标志位为5,长整形字面量CONSTANT_Double_infotag标志位为6,双精度字面量CONSTANT_Class_infotag标志位为7,类或接口的符号引用CONSTANT_String_infotag标志位为8,字符串类型的字面量CONSTANT_Fieldref_infotag标志位为9,字段的符号引用CONSTANT_Methodref_infotag标志位为10,类中方法的符号引用CONSTANT_InterfaceMethodref_infotag标志位为11,接口中方法的符号引用CONSTANT_NameAndType_infotag标志位为12,字段和方法的名称以及类型的符号引用6)u2access_flags表示类或者接口的访问信息,具体如下图所示:7)u2this_class表示类的常量池索引,指向常量池中CONSTANT_Class_info的常量8)u2super_class表示超类的索引,指向常量池中CONSTANT_Class_info的常量9)u2interface_counts表示接口的数量10)u2interface[interface_counts]表示接口表,它里面每一项都指向常量池中CONSTANT_Class_info常量11)u2fields_count表示类的实例变量和类变量的数量12)field_infofields[fields_count]表示字段表的信息,其中字段表的结构如下图所示:上图中access_flags表示字段的访问表示,比如字段是public,private,protect等,name_index表示字段名称,指向常量池中类型是CONSTANT_UTF8_info的常量,descriptor_index表示字段的描述符,它也指向常量池中类型为CONSTANT_UTF8_info的常量,attributes_count表示字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。13)u2methods_count表示方法表的数量14)method_info表示方法表,方法表的具体结构如下图所示:其中access_flags表示方法的访问表示,name_index表示名称的索引,descriptor_index表示方法的描述符,attributes_count以及attribute_info类似字段表中的属性表,只不过字段表和方法表中属性表中的属性是不同的,比如方法表中就Code属性,表示方法的代码,而字段表中就没有Code属性。其中具体Class中到底有多少种属性,等到Class文件结构中的属性表的时候再说说。15)attribute_count表示属性表的数量,说到属性表,我们需要明确以下几点:属性表存在于Class文件结构的最后,字段表,方法表以及Code属性中,也就是说属性表中也可以存在属性表属性表的长度是不固定的,不同的属性,属性表的长度是不同的上面说完了Class文件结构中每一项的构成以后,我们以一个实际的例子来解释以下上面所说的内容。复制代码代码如下:packagecom.ejushang.TestClass;publicclassTestClassimplementsSuper{privatestaticfinalintstaticVar=0;privateintinstanceVar=0;publicintinstanceMethod(int){returnparam+1;}}interfaceSuper{}通过jdk1.6.0_37的javac编译后的TestClass.java对应的TestClass.class的二进制结构如下图所示:下面我们就根据前面所说的Class的文件结构来解析以下上图中字节流。1)魔数从Class的文件结构我们知道,刚开始的4个字节是魔数,上图中从地址00000000h-00000003h的内容就是魔数,从上图可知Class的文件的魔数是0xCAFEBABE。2)主次版本号接下来的4个字节是主次版本号,有上图可知从00000004h-00000005h对应的是0×0000,因此Class的minor_version为0×0000,从00000006h-00000007h对应的内容为0×0032,因此Class文件的major_version版本为0×0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。3)常量池的数量接下来的2个字节从00000008h-00000009h表示常量池的数量,由上图可以知道其值为0×0018,十进制为24个,但是对于常量池的数量需要明确一点,常量池的数量是constant_pool_count-1,为什么减一,是因为索引0表示class中的数据项不引用任何常量池中的常量。4)常量池我们上面说了常量池中有不同类型的常量,下面就来看看TestClass.class的第一个常量,我们知道每个常量都有一个u1类型的tag标识来表示常量的类型,上图中0000000ah处的内容为0x0A,转换成二级制是10,有上面的关于常量类型的描述可知tag为10的常量是Constant_Methodref_info,而Constant_Methodref_info的结够如下图所示:其中class_index指向常量池中类型为CONSTANT_Class_info的常量,从TestClass的二进制文件结构中可以看出class_index的值为0×0004(地址为0000000bh-0000000ch),也就是说指向第四个常量。name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0×0013,表示指向常量池中的第19个常量。接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap-verboseTestClass即可得到所有常量池中的常量,截图如下:从上图我们可以清楚的看到,TestClass中常量池有24个常量,不要忘记了第0个常量,因为第0个常量被用来表示Class中的数据项不引用任何常量池中的常量。从上面
本文标题:实例分析JavaClass的文件结构
链接地址:https://www.777doc.com/doc-2499497 .html