JVM--Class文件结构

一、代码示例

  后面的代码举例都已如下代码示例

package org.fenixsoft.clazz;
public class TestClass {
    private int m;
    public int inc() {
        return m + 1;
    }
}

  编译后的class文件,使用16进制文本打开(我使用的是UItraEdit),内容如下:

    

二、class文件结构简述

  class文件由魔数、版本号(副版本号、主版本号)、常量池信息(常量池计数器和常量池数据区)、访问标志、类索引(当前类索引、父类索引)、接口数据区(接口数量、接口信息)、方法数据区(方法数量、方法信息)、属性数据区(属性数量、属性信息组成)组成。

  具体的内容如下表所示,无符号数使用u1、u2、u4、u8分别代表1、2、4、8个字节,如果有多个无符号组成的表,则以  _info  结尾表示(有符号可以类比为java中的简单基本数据类型,而有多个无符号组成的表可以类比为java中的一个对象,对象中都是基本数据类型的属性)

    

   结构如下所示:

      

三、魔数与版本信息

  1、魔数:由上面的表格可以看到,魔数占用4个字节,其值固定为CAFEBABY,与示例中的值一样

  2、副版本号(次版本号):占用2个字节,目前没有使用,为 00 00

    3、主版本号:占用两个字节。JAVA的其实版本号为45,即JDK1的版本号为45,每一个大版本号加一;高版本jdk可以运行低版本jdk编译的class文件,反之则不可以。以上面的示例代码为例,其版本为十六进制的0x0034,即十进制的52,那么编译class文件的jdk版本则为jkd8(52-45+1)

四、常量池数据区

(一)常量池存储信息  

  紧跟着主次版本号后面的就是常量池信息。

    

  常量值中主要存放两类信息:字面量和符号引用。

    字面量比较接近java中常量的意思,如文本字符串、被final修饰的常量等;

    而符号引用则属于编译原理方面的概念,主要包括:

      1、被模块到处或者开放的包

      2、类和接口的全限定名

      3、字段的名称和描述符

      4、方法句柄和方法类型

      5、动态调用点和动态常量

  最早的常量表中有11种结构不相同的表结构数据,后来为了支持动态语言调用,加入了4种,后来又为了支持模块化,又加入了两种,因此至JDK13,共有17种表结构数据。

  每一种表结构数据最少有两部分组成,tag和info,其中tag是用来标记不同表结构的,info用来表示具体的信息。具体的info信息,可以看最后的常量表结构。

(二)int和float常量存储结构  

  首先对于对于基本数据类型和String的值对应的info信息是具体的值,例如int和float的结构如下所示:tag分别为3和4,然后使用4个字节来表示值。

    

(三)long和double常量存储结构  

  对于long和double类型,tag分别为5和6,并且使用8个字节存储其值,其中前4个字节为高位数,后4个字节为低位数。

      

(四)String常量存储结构  

  对于String类型的数据,tag为8,info为两个字节的字符串索引项(也就是下面要说的utf8格式的cp_info)

     

(五)utf8常量存储结构  

  上面String的info信息即是对一个UTF8格式索引项,那么utf8格式的常量,tag为1,然后使用两个字节存储utf8编码字节数组的长度,然后使用具体的长度的数组存储数据。

      

   对于String存储具体是怎么样的,可以参照下图,就是定义了一个String,其字符串为“JVM原理”,那么JVM原理是存储在utf8格式的cp_info中,在utf8的cp_info中,存储了tag,长度,及具体的字符串内容;然后在String的cp_info中,只是存储了对于utf8的cp_info的索引项。

    

(六)类存储结构  

  而对于类、方法、属性的cp_info则是由类或接口的索引项和类型名称的索引项。

  以类为例,其tag为7,index为占用两个字节的类名称索引项,对应的也是一个utf格式的cp_info

    

  常量表详情:

常量名 描述 项目(无符号数) 类型 描述
CONSTANT_Utf8_info utf8编码的字符串 tag u1 值为1
length u2 utf8的字符串占用了多少字节数
bytes u1 长度为length的utf8编码的字符串
CONSTANT_Integer_info 整型字面量 tag u1 值为3
bytes u4 按照高位在前存储的int值
CONSTANT_Float_info 浮点数字面量 tag u1 值为4
bytes u4 按照高位在前存储的float值
CONSTANT_Long_info 长整型字面量 tag u1 值为5
bytes u8 按照高位在前存储的long值
CONSTANT_Double_info 双精度浮点型字面量 tag u1 值为6
bytes u8 按照高位在前存储的double值
CONSTANT_Class_info 类或接口的符号引用 tag u1 值为7
index u2 指向全限定名常量项的索引
CONSTANT_String_info 字符串类型字面量 tag u1 值为8
index u2 指向字符串字面量的索引
CONSTANT_Fieldref_info 字段的符号引用 tag u1 值为9
index u2 指向字段声明类或接口描述符CONSTANT_Class_info的索引
index u2 指向字段描述符CONSTANT_NameAndType的索引
CONSTANT_Methodref_info 类中方法的符号引用 tag u1 值为10
index u2 指向方法声明类描述符CONSTANT_Class_info的索引。
index u2 指向名称及类型描述符CONSTANT_NameAndType的索引
CONSTANT_InterfaceMethodref_info 接口中方法的引用 tag u1 值为11
index u2 指向方法声明接口描述符CONSTANT_Class_info的索引
index u2 指向名称及类型描述符CONSTANT_NameAndType的索引
CONSTANT_NameAndType_info 字段或方法的部分符号引用 tag u1 值为12
index u2 指向该字段或方法名称常量项的索引
index u2 指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info 表示方法句柄 tag u1 值为15
reference_kind u1 值必须在1-9之间,闭区间,它决定了方法句柄的类型,方法句柄类型值表示方法句柄的字节码行为
reference_index u2 值必须是对常量池的索引
CONSTANT_MethodType_info 表示方法类型 tag u1 值为16
descroptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info
CONSTANT_Dynamic_info 表示一个动态计算常量 tag u1 值为17
bootstrap_method_attrindex u2 值必须是当前class文件中引导方法表的bootstrap_method[]数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符
CONSTANT_InvokeDynamic_info  表示一个动态方法调用点 tag u1 值为18
bootstrap_method_attrindex u2 值必须是当前class文件中引导方法表的bootstrap_method[]数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符
CONSTANT_Module_info    表示一个模块 tag u1 值为19
name_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示模块名称
 CONSTANT_Package_info  表示一个模块中开放或者导出的包    tag u1  值为20
 name_index u2  值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示模块名称

(七)哪些字面量会进入常量池

  结论:

    1、final修饰的8种基本数据类型都会进入常量池

    2、非final修饰的8种基本数据类型,只有double、long、float的值会进入常量池。

    3、常量池种包含的字符串类型字面量(双引号引起来的字符串值)

五、访问标志和类索引(当前类索引、父类索引)

  1、访问标志

  在常量池结束以后,紧接着两个字节表示访问标志,这个标志用于识别一些类或者接口层次的访问信息,例如是否为public、是否为abstract、如果是类的话是否被声明为final等等,具体标志位及含义如下所示

     

  2、类索引、父类索引、接口集合

  类索引和父类索引都是一个u2类型的数据,接口索引集合是一组u2类型的数据集合,class文件中由这三项数据来确定该类型的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类父类的全限定名。接口索引集合用来描述这个类实现了哪些接口。

六、字段表集合和放发表集合

(一)字段表集合 

  字段表集合用于描述接口或者类中声明的变量。其包含类变量(static修饰)和实例变量。

  字段可以包括的修饰符有字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。

  

   字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常类似的,都是一个u2的数据类型。

  

   跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池项的引用,分别代表着字段的简单名称以及字段和方法的描述符。

  基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示

   

  举例说明

  

(二)方法表集合

  这里跟上面的字段表很类似,就不再多说,直接上表

  

   

 (三)属性表集合

  属性表(attribute_info),Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。

  

      

七、javap命令

  可以使用javap命令反编译class文件,例如使用javap命令反编译示例的class文件:javap -v TestClass

      

 

  从输出结果看,首先是警告,然后是文件地址、最后修改时间、文件大小、MD5加密、包名、次版本号、主版本号、访问标志等内容。

  然后就是常量池内容:

    1、第一个内容是方法的cp_info,备注里面可以看到,这个方法名是init方法,其有两个索引项,方法对应的类或接口和方法的NameAndType,其分别是4和15

        先来看4,说明其是一个类,从备注可以看到该类是Object类,其对应18的索引项,指向了Object类;

        再来看15,其是方法的NameAndType,分别对应7和8,7说明该方法名称是init方法,8说明该方法返回值为void

    2、第二个内容是字段的cp_info,存在对应类或接口的索引项和字段的NameAndType,分别对应3和16

        先来看3,其是一个类,有全限定名的索引项,指向了17,再看17,其类为TestClass类。

        再来看16,是一个NameAndType的索引项,其中包含了名称和类型的索引项,对应5和6,5说明了该字段名称为m,6说明了该字段的类型是int

    3、再往下就是code区,及code区中方法信息。

    4、然后看具体的init方法,其标识了方法的返回值、访问标志、栈深等信息,然后就是具体的操作,使用aload将1压进栈,再使用getfield将获取m,然后使用iconst将m压进栈,然后使用iadd相加,最后使用ireturn返回结果。

posted @ 2021-06-24 14:26  李聪龙  阅读(83)  评论(0编辑  收藏  举报