概述本文主要是根据.class文件来分析.class文件的内容。我个人认为这部分主要属于设计机构拓展的内容。可以学习Java字节码的设计结构,感受设计者的设计。Class类文件结构Java提供了javap命令来分析字节码文件。当我们使用javap-verbose命令分析字节码文件时,会分析字节码文件的幻数、版本号、常量池、类信息。、类的构造方法、类中的方法信息、类变量和成员变量等信息。一个简单的Java代码publicclassTestClass{privateintm;publicintinc(){return++m;}}下图是Java代码编译后.class文件的十六进制信息bytecode_hexadecimal.png,方便对比。执行一下javap-vTestClassClassfile/../../TestClass.classLastmodified2021-2-6;size306bytesMD5checksumeeba40cc40cc28ef4d416ff70d901561编译自“TestClass.java”publicclasscn.edu.cqvie.jvm.bytecode.TestClassminorversion:0majorversion:52flags:ACC_PUBLIC,ACC_SUPER#1#1Constantpool#1#1Constantpool//java/lang/Object."":()V#2=Fieldref#3.#16//cn/edu/cqvie/jvm/bytecode/TestClass.m:I#3=Class#17//cn/edu/cqvie/jvm/bytecode/TestClass#4=Class#18//java/lang/Object#5=Utf8m#6=Utf8I#7=Utf8#8=Utf8()V#9=Utf8Code#10=Utf8LineNumberTable#11=Utf8inc#12=Utf8()I#13=Utf8SourceFile#14=Utf8TestClass.java#15=NameAndType#7:#8//"":()V#16=NameAndType#5:#6//m:I#17=Utf8cn/edu/cqvie/jvm/bytecode/TestClass#18=Utf8java/lang/Object{publiccn.edu.cqvie.jvm.bytecode.TestClass();描述符:()Vflags:ACC_PUBLICode:stack=1,locals=1,args_size=10:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnLineNumberTable:line3:0publicintinc();描述符:()iflags:ACC_PUBLICode:stack=3,locals=1,args_size=10:aload_01:dup2:getfield#2//Fieldm:I5:iconst_16:iadd7:dup_x18:putfield#2//Fieldm:I11:ireturnLineNumberTable:line8:0}源文件:“TestClass.java”Java字节码结构bytecode_structure.png1。Magicnumber和Class文件版本Magicnumber:所有.class字节码文件的前4个字节都是magicnumber,是固定的Value:0xCAFEBABE版本信息,magicnumber后面的4个字节是版本信息,前两个字节代表minorversion(minorversionnumber),最后2个字节代表majorversion(主版本号),这里版本号00000034转换成十进制表,表示次版本号为0,主版本号为52。因此,该文件的版本号为1.8.0。这可以通过java-version来验证。?~java-versionjavaversion"1.8.0_281"Java(TM)SERuntimeEnvironment(build1.8.0_281-b09)JavaHotSpot(TM)64-BitServerVM(build25.281-b09,mixedmode)2.常量池常量池:2+N字节紧跟在主版本号之后的是常量池条目。一个java类中定义的很多信息都是通过常量池来描述的。常量池可以看作是Class文件的资源仓库。比如Java类中的方法和变量的变量信息就存放在常量池中。常量池中主要存储两类常量:字面量和符号引用。字面量,如字符串字面量、java中声明为final的常量值等符号引用,如类和接口的全局限定名、字段名和描述符、方法名和描述符等常量的整体结构pool:Java类对应的常量池,主要由常量池(常量表)的编号和常量池数组组成。常量池中的常量个数跟在主版本号后面,占2个字节:常量池长度比如我们这里的十六进制是0013,也就是说有18个常量。常量数组紧跟在常量池编号之后。常量池数组与一般数组的区别在于常量池数组中不同元素的类型和结构不同。当然,长度也不同;但是,一个元素的第一个元素的第一个数据是u1类型,这个字节是一个标识位,占用1个字节。JVM在解析常量池的时候,会通过这个u1类型来获取元素的具体类型。值得注意的是:常量池的元素个数=常量池个数-1(其中0暂时不适用),目的是为了满足某些常量池索引值的数据在某些情况下需要表达[不要引用任何常量池]意思:根本原因是0的索引也是一个常量(保留常量),但它不位于常量表中。这个常量对应的是空值,所以常量池的索引从1开始,而不是从0开始。上表描述了机构的11种数据类型,实际上在jdk1.0之后又增加了3种(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynami_info)。7.共有14种。在JVM规范中,每个变量/字段都有描述信息,描述信息的主要作用是描述字段的数据类型、参数列表(包括数量、类型、顺序)和方法的返回值。根据描述符规则,基本数据类型和表示无返回值的void类型用大写字符表示,对象类型用字符L加上对象全限定名表示。为了压缩字节码文件的大小,对于基本数据类型,JVM只用一个大写字母来表示,如下:B-byte,C-char,D-double,F-float,I-int,J-long,S-short,Z-boolean,V-voidL-表示对象类型,如:Ljava/lang/String;对于数组类型,每个维度用前面的[表示,比如int[]表示为[I,String[][]表示为[[java/lang/String;用描述符描述方法时,是按照参数列表先描述,再返回值的顺序描述的。参数列表严格按照参数的顺序放在group()中,比如方法:StringgetRealnameByIdNickname(intid,Stringname),描述符为:(I,Ljava/lang/String;)有Ljava/lang/StringClass字节码字节数据中的两种数据类型直接Volume:这是基本数据类型。细分为u1、u2、u4、u8四种类型,分别表示由1字节、2字节、4字节、8字节组成的连续整体数据。表(数组):表示由多个基础数据或其他表按预定顺序组成的大数据集合。表是结构化的。其结构体现在对组成表格的部件的位置和顺序进行了严格的定义。常量池常量0A0004000Fmethod_info0004指向常量在常量池中的位置,000F指向15的位置。0900030010field_info0003指向position30010(position16)070011classinfo0011(position17)070012classinfo0012(position18)0100016Dutf8lengthis1contentis6D表示内容转为十进制,转为109,转为ASCII码,最终结果为m01000149utf8长度为1,内容为I0100063C696E69743Eutf8长度为8,内容为010003282956utf8长度为3内容:()V010004436F6465utf8长度为4内容为Code01000F4C696E654E756D6265725461626C65utf8长度为15内容为LineNumberTable010003696E63utf8长度为3内容为inc010003282949长度为3内容为()I01000A536F7572636546696C65长度为10,内容为SourceFile01000E54657374436C6173732E6A617661长度为14,内容为TestClass.java0C00070008NameAndType内容指向7个位置。0008(位置8)0C00050006NameAndType内容指向位置5。0006(6个位置)010023636E2F6564752F63717669652F6A766D2F62797465636F64652F54657374436C6133长度/73浏览内容jvm/bytecode/TestClass0100106A6176612F6C616E672F4F626A656374长度16内容java/lang/Object3.以及接口是否定义为public,是否是abstract,如果是类,是否声明为final。源代码来自粉丝。0x0021:表示0x0020和0x0001的并集,表示ACC_PUBLIC和ACC_SUPERbytecode_accessflag.png4。类索引,父类索引0003类名,03常量池位置cn/edu/cqvie/jvm/bytecode/TestClass0004父类名。04常量池位置java/lang/Object0000接口数,0,最大接口数655355字段表集合0001字段数,这里是bytecode_fieldtable.png字段表结构field_info{u2access_flags;0002Fieldrefu2name_index;0005(表示字段名在常量池中的索引位置)mu2descriptor_index;0006(描述符索引)Iu2attributes_count;0000attribute_infoattributes[attributes_count]}6.methodtableset0002表示有两个methodbytecode_method表。png方法表结构method_info{u2access_flags;0001Methodrefu2name_index;//0007u2descriptor_index;0008//()Vu2attributes_count;0001//属性结构attributes_infoattributes[attributes_count]}方法属性结构attribute_info{u2attribute_name_index;0009//Codeu4attribute_length;0000001D长度29u1info[attribute_length];...}7。属性表集合000900001D00010001000000052AB70001B100000001000A0000000600010000000300"代码"表示下面是一些预定义的属性JVM用于执行代码,但编译器也可以实现自己的属性写入class文件中,供运行时使用。不同的属性通过attribute_name_index来区分。JVM规范预定义的attributeCode结构Code属性的作用是保存要放置的结构,比如对应的字节码Code_attribute{u2attribute_name_index;//0009==>Codeu4attribute_length;//0000001D==>29u2max_stack;//0001stack深度为1(栈帧中操作数栈的深度)u4code_length;//00000005指令长度为u1code[code_lenght];//2AB70001B1其中2A//2Aaload_0//B7invokespecial//00#1//01>//B1return//0aload_0//1invokespecial#1>//4returnu2exception_table_length;//0000{u2start_pc;u2end_pc;u2handler_pc;u2catch_type;}exception_table[exception_table_length];u2attributes_count;//0001attribute_infoattributes[attributes_count];}attribute_length表示个数属性中包含的字节数,不包括attribute_name_index和attribute_length字段。max_stack表示在这个方法运行的任何时候可以达到的操作数栈的最大深度。max_locals表示方法执行过程中创建的局部变量的个数,包括用来表示传入参数的局部变量。code_length表示方法中包含的字节码的字节数和具体的指令代码。具体的字节码就是调用方法时虚拟机执行的字节码。exception_table,存放异常信息。每个exception_table存储异常处理信息。每个exception_table条目由start_pc、end_pc、handler_pc、catch_type组成。start_pc和end_pc表示代码数组中从start_pc到end_pc的指令抛出的异常(包括start_pc,不包括end_pc)将由该入口处理。handler_pc表示处理异常的代码的开头。catch_type表示将要处理的异常类型,它指向常量池中的一个异常类。当catch_type为0时,表示处理所有异常。LineNumberTable结构LineNumberTable_attribute{u2attribute_name_index;//000A常量池第10位LineNumberTableu4attribute_lenght;//00000006共6个长度u2line_number_table_length;//0001映射对数1对line_number_info{u2start_pc;//指令行号000003}line_number_table_table[line_number];}局部变量表LocalVariableTableLocalVariableTable_attribute{u2attribute_name_index;u4attribute_lenght;u2local_variable_table_length;{u2start_pc;u2line_number;u2name_index;u2descriptor_index;u2index;}}如果成员在构造方法中初始化了默认值,那么会初始化默认成员,构造方法仍然赋值在构造方法中,这是指令的重新排序。如果有多个构造方法,每个构造方法都有初始化成员变量的属性,保证每个构造方法初始化时都会执行属性的初始化过程。如果构造方法中有执行语句,那么会先执行赋值信息,再执行自定义的执行代码。对于每一个Java实例方法(非静态方法),在编译后生成的字节码中都比实际的方法多了一个参数,位于方法的第一个参数位置。我们可以在当前方法中访问this对当前对象中this的操作是在Javac编译器编译时,将对this的访问转换为普通实例方法的参数访问,然后在JVM运行时自动传递给实例方法在运行时调用实例方法的this参数,所以在实例方法的局部变量表中,至少会有一个局部变量指向当前对象。字节码处理异常的方式:统一使用异常表来处理异常。在Jdk1.4.2之前的版本中,并没有使用异常表来处理异常,而是通过具体的指令来处理。当异常处理中有finally语句块时,现代JVM只是利用finally语句块本身拼接在每个catch块后面。也就是说,如果程序中有多个catch块,每个catch块都会在块之后重复多少个finally语句块字节码。