当前位置: 首页 > 后端技术 > Java

最通俗易懂的Class文件结构(上)

时间:2023-04-02 00:33:54 Java

什么是Class文件?Java刚刚诞生的时候,就提出了一个非常有名的口号:“WriteOnce,RunAnywhere。(WriteOnce,RunAnywhere)”。为了实现平台无关性,各种平台上的虚拟机都使用统一的程序存储格式,这就是字节码(ByteCode)。它以二进制字节流的形式存储在Class文件中,其中包含了Java虚拟机的指令集和符号表,以及其他一些辅助信息。为什么需要了解Class文件结构?一般数据结构的分享难免枯燥,但是了解Class文件结构是了解Java虚拟机的重要基础之一。要想对Java虚拟机有更深入的了解,那么Class文件结构是一定要接触的。我会在保证逻辑准确的基础上,结合实际案例,尽量分享得通俗易懂。Class文件结构介绍Class文件是一组基于8位字节的二进制流。每个数据项都严格按照顺序排列在Class文件中,中间没有任何分隔符。当遇到超过8位字节的数据时,按照高位的方式分成多个8位字节存储(最高字节从低地址开始存储,最低字节存储在最高地址的顺序)。.Class文件格式使用类似于C语言结构的伪结构来存储数据。这个伪结构有两种数据类型:无符号数和表格。无符号数用u1、u2、u4、u8分别表示1字节、2字节、4字节、8字节的无符号数,可用于描述数字、索引引用、量化值或UTF-8编码的字符串价值。表是由多个无符号数或其他表作为数据项组成的复合数据类型,所有表习惯性以“_info”结尾。表的数据结构与树非常相似。无符号数相当于它的叶子节点,其他表相当于它的子节点。整个Class文件本质上是一张表,具体结构如下:类型名称描述u4magic1幻数u2minor_version1次版本号u2major_version1主版本号u2constant_pool_count1常量池容量计数值cp_infoconstant_poolconstant_pool_count-1常量池u2access_flags1访问标志u2this_class1类索引u2super_class1parentclassindexu2interfaces_count1interfaceindexcountvalueu2interfacesinterface_countinterfaceindexu2fields_count1fieldcountvaluefield_infofieldsfields_countfieldu2methods_count1methodcountvaluemethod_infofieldsmethods_countmethodu2attributes_count1attributecountvalue此时使用一个前置计数器来添加几个连续的数据项。这时候,我们把这一系列连续的某一类型的数据称为该类型的集合。在Class文件中,无论顺序还是数量,甚至数据存储的字节顺序,都必须严格按照上表设置。哪个字节代表什么意思,长度和顺序是不允许改变的。.我们来看看每个数据项的具体含义。幻数(MagicNumber)是每个Class文件的前4个字节,用来判断当前文件是否是Java虚拟机接受的Class文件。许多文件存储标准使用幻数进行识别。例如,gif和jpeg等图像文件都有幻数。使用幻数而不是扩展名是出于安全原因,因为扩展名更容易修改。文件格式创建者可以自由选择幻数,只要幻数没有被广泛使用并且不会混淆其他文件。Class文件的幻数是:0xCAFEBABE(咖啡宝贝?),这个幻数是在Java还被称为“Oak”语言的时候(大概是1991年)确定的,据Java开发的原始关键成员PatrickNaughton说团队表示:“我们一直在寻找有趣且容易记住的东西。我们选择了0xCAFEBABE,因为它象征着著名咖啡品牌Peet'sCoffee中广受欢迎的Baristas咖啡。”他们真的很喜欢喝咖啡,这也可能预示着未来“Java”这个名字的出现。为了更快的理解,我准备了一个实际案例,一段非常简单的Java代码:publicclassOneMoreStudy{privateintnumber;privateintplusOne(){返回数字+1;}}使用JDK1.7将这段代码编译成Class文件,用HexEd打开,可以得到magicnumber,如下图所示:在接下来的分享中,会经常用到这个Class文件。次要版本号和主要版本号后跟幻数。第5、6字节为次版本号(MinorVersion),第7、8字节为主版本号(MajorVersion)。Java的主版本号从45开始,JDK1.1之后,JDK每个大版本的主版本号加1,高版本的JDK向下兼容低版本的Class文件,但是不能运行更高版本的类文件,即使Class文件的格式没有发生任何改变,Java虚拟机也会拒绝运行超过其版本号的Class文件。再看前面的Class文件例子:次版本号的第5、6字节的值为0x0000,主版本号的第7、8字节的值为0x0033,十进制为51。这个Class文件可以在JDK1.7及以上版本的Java虚拟机上运行。常量池后面是主版本号。可以理解为Class文件的资源仓库,也是Class文件结构中与其他数据项关联最多的数据类型。因为常量池的常量个数不固定,所以首先有一个u2类型的数据,表示常量池的容量(constant_pool_count)。常量池的容量计数不是从0开始,而是从1开始。这是因为0有它的特殊用途,就是表示在特殊情况下“不引用任何常量池项”的意思。.在Class文件结构中,只有常量池的容量计数从1开始,其他集合,接口索引集合、字段集合、方法集合等的容量计数都是从0开始的,我们看前面的Class再举个文件例子:常量池容器计数值为0x0013,十进制为19,表示常量池中有18个常量,索引值范围为1~18,其中主要存储两类常量常量池:文字和符号引用。字面量在Java语言层面更接近常量,例如文本字符串和声明为final的常量值。符号引用是编译原理级别的概念,包括以下三种类型:类和接口的完全限定名字段的名称、描述符方法的名称、描述符常量池的名称。常量池中的每个常量都是一个表,一共有14种不同的类型。常量类型(JDK1.7及更早版本),每种类型的表在首位都有一个u1类型标志,如下表所示:长整数字面量CONSTANT_Double_info6双精度浮点字面量CONSTANT_Class_info7类或接口符号引用CONSTANT_String_info8字符串类型字面量CONSTANT_Fieldref_info9字段符号引用CONSTANT_Methodref_info10类中方法的方法引用CONSTANT_Methodref_info10MethodinfoCONSTANT符号引用CONSTANT_NameAndType_info12字段或部分方法符号引用CONSTANT_Method_info1表示方法句柄handleCONSTANT_MethodType_info16标识方法类型CONSTANT_InvokeDynamic_info18表示动态方法调用点有一个工具javap专门分析Class文件的字节码。让我们用它来直接看前面Class文件例子中的18个常量(常量池以外的信息已省略):E:\>javap-verboseOneMoreStudyCompiledfrom"OneMoreStudy.java"minorversion:0majorversion:51常量池:#1=Methodref#4.#15//java/lang/Object."":()V#2=Fieldref#3。#16//OneMoreStudy。编号:I#3=Class#17//OneMoreStudy#4=Class#18//java/lang/Object#5=Utf8number#6=Utf8I#7=Utf8#8=Utf8()V#9=Utf8Code#10=Utf8LineNumberTable#11=Utf8plusOne#12=Utf8()I#13=Utf8SourceFile#14=Utf8OneMoreStudy.java#15=NameAndType#7:#8//"":()V#16=NameAndType#5:#6//number:I#17=Utf8OneMoreStudy#18=Utf8java/lang/Object其中有些常量在代码中好像从来没有出现过,比如"I","","Code",“LineNumberTable”、“SourceFile”其实是自动生成的,被后面要分享的字段表、方法表、属性表引用,用来描述一些不方便用“固定字节”表达的内容。accessflag后面跟常量池的2个字节代表访问标志(access_flags),用来标识一些类或者接口级别的访问信息,具体见下表:ACC_FINAL0x0010声明为finalACC_SUPER0x0020是否允许使用invokespecial字节码指令ACC_INTERFACE0x0200是否为接口ACC_ABSTRACT0x0400是否为抽象类型ACC_SYNTHETIC0x1000Flag此类不是用户代码生成的ACC_ANNOTATION0x2000是否为注解ACC000是否为enJum0x40.2编译1Class文件必须真实;ACC_ABSTRACT对接口或抽象类为真,对其他类为假。前面的例子OneMoreStudy是一个普通的类,不是接口,不是注解也不是枚举,只是被public修饰,没有声明为final或者abstract,并且是用JDK1.7编译的,所以只有ACC_PUBLIC和ACC_SUPER为真,所以它的access标识应该是0x0001|0x0020=0x0021,如下图:由于下一章篇幅有限,分享就先到此为止,希望大家能够更好的消化吸收。想知道接下来发生了什么,就请听下一章来分解吧!敬请关注!推荐阅读:Class文件结构综合分析(下)