美文网首页java学习之路
JavaGuide知识点整理——类文件结构详解

JavaGuide知识点整理——类文件结构详解

作者: 唯有努力不欺人丶 | 来源:发表于2022-08-28 16:28 被阅读0次

    概述

    在java中,JVM可以理解的代码就叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题。同时又保留了解释型语言可移植的特点。所以java程序运行时比较高效,而且由于字节码并不针对一种特定的机器,因此java程序无需重新编译便可在多种不同操作系统的计算机上运行。
    Clogure,Groovy,Scala等语言都是运行在java虚拟机之上。下图展示了不同语言被不同的编译器编译成.class文件最终运行在java虚拟机之上。.class文件的二进制格式可以用啥WinHex查看。


    image.png

    可以说.class文件是不同的语言在java虚拟机之间的重要桥梁。同时也是支持java跨平台的很重要的一个原因。

    Class文件结构总结

    根绝java虚拟机规范,Class文件通过ClassFile定义,有点类似C语言的结构体。
    ClassFile的结构如下:

    ClassFile {
        u4             magic; //Class 文件的标志
        u2             minor_version;//Class 的小版本号
        u2             major_version;//Class 的大版本号
        u2             constant_pool_count;//常量池的数量
        cp_info        constant_pool[constant_pool_count-1];//常量池
        u2             access_flags;//Class 的访问标记
        u2             this_class;//当前类
        u2             super_class;//父类
        u2             interfaces_count;//接口
        u2             interfaces[interfaces_count];//一个类可以实现多个接口
        u2             fields_count;//Class 文件的字段属性
        field_info     fields[fields_count];//一个类可以有多个字段
        u2             methods_count;//Class 文件的方法数量
        method_info    methods[methods_count];//一个类可以有个多个方法
        u2             attributes_count;//此类的属性表中的属性数
        attribute_info attributes[attributes_count];//属性表集合
    }
    

    通过分析ClassFile的内容,我们便可以知道class文件的组成。

    class文件的组成

    下面是通过idea插件jclasslib查看的:


    image.png

    使用jclasslib不光可以直观的查看某个类对应的字节码文件,还可以查看类的基本信息,常量池,接口,属性,函数等信息。
    下面详细介绍一个class文件结构涉及到的一些组件。

    魔数(Magic Number)

        u4             magic; //Class 文件的标志
    

    每个class文件的头四个字节称为魔数,它唯一的作用就是确定这个文件是否为一个能被虚拟机接收的Class文件。
    程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其它特殊的含义。

    Class文件版本号(Minor&Major Version)

        u2             minor_version;//Class 的小版本号
        u2             major_version;//Class 的大版本号
    

    紧接着魔数的四个字节存储的是Class文件的版本号,第五和第六位是次版本号,第七和第八位是主版本号。
    每当java发布大版本(比如java8,java9)的时候,主版本号都会+1,你可以使用javap -v命令来快速查看Class文件的版本号信息。
    高版本的java虚拟机可以执行低版本编译器生成的Class文件,但是低版本的java虚拟机不能执行高版本编译器生成的Class文件。所以我们在实际开发的时候要确保开发的JDK版本和生产环境的JDK版本保持一致。

    常量池(Constant Pool)

        u2             constant_pool_count;//常量池的数量
        cp_info        constant_pool[constant_pool_count-1];//常量池
    

    紧接着主次版本号之后的是常量池,常量池的数量是constant_pool_count-1(常量池计数器是从1开始计数的。将第0项空出来是有特殊考虑的,索引值为0代表不用引用热河一个常量池项)。
    常量池主要存放两大常量:字面量和符号引用。字面量比较接近于java语言层面的常量概念。如文本字符串,声明为funal的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
      常量池中每一项常量都是一个表,这14种表有一个共同的特点:开始的第一位是一个u1类型的标志位。-tag来表示常量的类型,代表当前这个常量属于哪种常量类型。
      image.png
      image.png
      image.png
      .class文件可以通过javap -v class类名指令来看一下其常量池中的信息。

    访问标志

    在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个class是类还是接口,是否为public或者abstract类型,如果是类的话是否声明为final等等。


    类访问和属性修饰符

    当前类(this class)父类(super class),接口(interfaces)索引集合

    u2 this_class;//当前类
    u2 super_class;//父类
    u2 interfaces_count;//接口
    u2 interfaces[interfaces_count];//一个类可以实现多个接口

    索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于java语言的单继承,所以父类索引只有一个,除了java.lang.Object之外,所有的java类都有父类,因此除了java.lang.Object外,所有的java类的父类索引都不为0。
    接口索引集合用来描述这个类实现了哪些接口,这些被实现的接口将按照implements后的接口顺序从左到右排列在接口索引集合中。

    字段表集合

    u2 fields_count;//Class 文件的字段的个数
    field_info fields[fields_count];//一个类会可以有个字段

    字段表用于描述接口或者类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。


    字段表的结构
    • access_flags:字段的作用域(public,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient修饰符),可变性(final),可见性(volitile修饰符,是否强制从主内存读写)。
    • name_index:对常量池的引用,表示字段的名称。
    • descriptor_index:对常量池的引用,表示字段和方法的描述符。
    • attributes_count:一个字段还会拥有一些额外的属性,attributes_count存放属性的个数。
    • attributes[attributes_count]:存放具体属性具体内容。

    上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示,而字段叫什么名字,字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。


    access_flag取值

    方法表集合

    u2 methods_count;//Class 文件的方法的数量
    method_info methods[methods_count];//一个类可以有个多个方法

    methods_count表示方法的数量,二method_info表示方法表。
    class文件存储格式中对方法的描述与对字段的描述几乎采用完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志,名称索引,描述符索引,属性表集合几项。


    方法表的结构 方法表的access_flag取值

    注意:因为volatile修饰符和transient修饰符只能修饰字段不能修饰方法,所以方法表的访问标志中没有这两个对应的标志。但是增加了synchronized,native,abstract等关键字修饰方法,所以也就多了这些关键字对应的标志。

    属性表集合

    u2 attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合

    在class文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息,与class文件中其他的数据项目要求的顺序,长度和内容不同。属性表集合的限制稍微宽松一些,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,java虚拟机运行时会忽略掉它不认识的属性。

    本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注~也祝大家工作顺顺利利,生活健健康康!

    相关文章

      网友评论

        本文标题:JavaGuide知识点整理——类文件结构详解

        本文链接:https://www.haomeiwen.com/subject/vkflwrtx.html