zoukankan      html  css  js  c++  java
  • 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返回结果。

    ------------------------------------------------------------------
    -----------------------------------------------------------
    ---------------------------------------------
    朦胧的夜 留笔~~
  • 相关阅读:
    用于视频行为识别的双流卷积网络
    python更改默认版本
    ubuntu16安装ROS(包括win10子系统ubuntu同样能用)
    js中的new做了什么?
    Promise是什么?
    滚动轴 scroll
    什么是ES6?
    什么是token及怎样生成token
    mongodb 删库跑路
    css如何设置背景图片?background属性添加背景图片
  • 原文地址:https://www.cnblogs.com/liconglong/p/14915032.html
Copyright © 2011-2022 走看看