• JVM之【字节码/Class文件/ClassFile 内容解析】


    说在前面的话

    字节码/Class文件/ClassFile 均为同一概念

    Java语言:跨平台的语言(write once,run anywhere)

    当Java源代码成功编译成字节码后,如果想在不同的平台上面运行,则无须再次编译这个优势不再那么吸引人了。Python、PHP、Perl、Ruby、Lisp等有强大的解释器。跨平台似乎已经快成为一门语言必选的特性。

    Java 虚拟机:跨语言的平台

    Java 虚拟机不和包括 Java 在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。无论使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行。可以说,统一而强大的Class文件结构,就是Java虚拟机的基石、桥梁。

    想要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码。

    • 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。
    • Javac是一种能够将Java源码编译为字节码的前端编译器。
    • Javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析语法解析语义解析以及生成字节码

    oracle的JDK软件包括两部分内容:

    • 将Java源代码编译成Java虚拟机的指令集的编译器
    • 用于实现Java虚拟机的运行时环境。

    字节码文件作用图示

    在这里插入图片描述

    JAVA代码到执行的过程图示

    在这里插入图片描述

    前端编译器 vs 后端编译器

    Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器

    Hotspot JVM并没有强制要求前端编译器只能使用javac来编译字节码,其实只要编译结果符合JVM规范都可以被JVM所识别即可。

    在Java的前端编译器领域,除了Javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的ECJ(EclipseCompiler for Java)编译器。和Javac的全量式编译不同,ECJ是一种增量式编译器。在Eclipse中,当开发人员编写完代码后,使用“ctrl+S”快捷键时,ECJ编译器所采取的编译方案是把未编译部分的源码逐行进行编译,而非每次都全量编译。

    因此ECJ的编译效率会比javac更加迅速和高效,当然编译质量和javac相比大致还是一样的。ECJ不仅是Eclipse的默认内置前端编译器,在Tomcat中同样也是使用ECJ编译器来编译jsp文件。由于ECJ编译器是采用GPLv2的开源协议进行源代码公开,所以,大家可以登录eclipse官网下载ECJ编译器的源码进行二次开发。

    默认情况下,Inteli IDEA 使用 javac 编译器。
    前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。

    在这里插入图片描述


    什么是字节码

    字节码文件/Class文件,是JVM的基石!!!

    字节码文件里是什么?

    源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码。

    什么是字节码指令(byte code)?

    Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。
    虚拟机中许多指令并不包含操作数,只有一个操作码。

    比如:

     0 aload_0
     1 invokespecial #1 <Father.<init> : ()V>
     4 aload_0 //只有操作码
     5 bipush 30 //操作码+操作数
     7 putfield #2 <Son.x : I>
    10 aload_0
    11 invokevirtual #3 <Son.print : ()V>
    14 aload_0
    15 bipush 40
    17 putfield #2 <Son.x : I>
    20 return
    

    概述

    Class 类的本质

    任何一个Class 文件都对应着唯一一个类或接口的定义信息,但反过来说,Class 文件实际上它并不一定以磁盘文件的形式存在。
    Class 文件是一组以8位字节为基础单位的二进制流。

    Class文件格式

    Class 的结构不像 XML 等描述语言,由于它没有任何分隔符号。所以在其中的数据项,无论是字节顺序还是数量,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变。

    Class 文件格式采用一种类似于 c语言结构体的方式进行数据存储

    • 这种结构中只有两种数据类型:无符号数
    • 无符号数:属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。
    • :是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“ info”结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上就是一张表。 由于表没有固定长度,所以通常会在其前面加上个数说明

    Class文件结构概述

    Class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对Class文件结构做出一些调整,但是其基本结构和框架是非常稳定的。

    Class文件的总体结构如下:

    1. 魔数
    2. Class文件版本
    3. 常量池
    4. 访问标志
    5. 类索引,父类索引,接口索引集合
    6. 字段表集合
    7. 方法表集合
    8. 属性表集合

    下图为官方给出的结构组成
    在这里插入图片描述
    结构字段解释
    在这里插入图片描述


    详细讲解

    1、魔数(Magic Number)

    • 每个 Class 文件开头的4个字节的无符号整数称为魔数(Magic Number)
    • 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。即:魔数是Class文件的标识符。
    • 魔数值固定为0xCAFEBABE。不会改变。
    • 如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验的时候就会直接抛出以下错误:Error: an error has occurred, please check your installation and try againException in thread “main” java.lang.classFormatError: Incompatible magic value 1885430635 in classfile stringTest
    • 使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。

    2、版本号

    • 紧接着魔数的 4 个字节存储的是 Class文件的版本号,也是4长个字节。

    • 其中前2个字节所代表的含义就是编译的副版本号minor version

    • 而后2个字节就是编译的主版本号major_version

    • 它们共同构成了Class文件的格式版本号。譬如某个 Class文件的主版本号为 M,副版本号为 m,那么这个Class文件的格式版本号就确定为 M.m。

    • 不同版本的Java编译器编译的Class文件对应的版本是不一样的

      • 高版本的Java虚拟机可以执行由低版本编译器生成的Class文件
      • 但是低版本的Java虚拟机不能执行由高版本编译器生成的Class文件。否则JVM会抛出java.lang.UnsupportedclassVersionError异常。
      • 因此,需要我们在开发时,特别注意开发编译的JDK版本和生产环境中的JDK版本是否一致。

    3、常量池

    • 常量池是Class文件中内容最为丰富的区域之一。常量池对于Class文件中的字段和方法解析也有着至关重要的作用。
    • 随着Java虚拟机的不断发展,常量池的内容也日渐丰富。可以说,常量池是整个class文件的基石
      在这里插入图片描述

    (1)常量池概述

    • 在版本号之后,紧跟着的是常量池的数量,以及若干个常量池表项。
    • 常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值(constant pool count)
    • 与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的。
    • 由上表可见,Class文件使用了一个前置的容量计数器(constant_pool count)加若干个连续的数据项(constant pool)的形式来描述常量池内容。我们把这一系列连续常量池数据称为常量池集合。
    • 常量池表项中,主要用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

    (2)常量池计数器

    • 由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值。
    • 常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即constant_pool_count=1表示常量池中有0个常量项
      举例,假如两个字节的值为:0x0016,也就是22。需要注意的是,这实际上只有21项常量。

    (3)索引为范围是1-21。为什么呢?

    • 通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。

    (4)常量池中的内容

    • 常量池主要存放两大类常量:字面量(Literal)和符号引用(SymbolicReferences)它包含了Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。
    • 常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte (标记字节、标签字节)。
    • 由于每个类型的长度都是已知的,因此只需要确定类型,即可顺利进行数据读取了。
    • 类型标志对应表格如下

    在这里插入图片描述

    1)字面量和引用类型
    1.字面量
    • 文本字符串
    • 声明为final的常量值
    String str = "ohyes";
    final int num = 10;
    
    2.符号引用
    • 类和接口的全限定名:com/CSDN/test/Demo这个就是类的全限定名,仅仅是把包名的".“替换成”/",为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。
    • 字段/方法的名称:简单名称是指没有类型和参数修饰的方法或者字段名称,上面例子中的类的add()方法和num字段的简单名称分别是add和num。
    • 字段/方法的描述符:描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:
      在这里插入图片描述

    举例说明

    String [] arr = new String[10];
    System.out.println(arr);
    // 输出结果应该为:[Ljava/lang/String;@xxxxxxx
    // [是数组,L是对象类型的引用,java/lang/String是指向的全限定名,@xxxxxxx就是对象具体的编号了
    
    long [][] arr = new long[10][10];
    System.out.println(arr);
    // 输出结果应该为:[[J@yyyyyyy
    // [[是数组,J是对象类型的引用,基本数据类型不需要限定名,@yyyyyyy就是对象具体的编号了
    
    int abc(int[] x, int y)
    // 描述符为:([II)I
    // ()表示方法,里面两个参数,一个[I,一个I,返回类型为I
    

    补充说明:

    • 虚拟机在加载Class文件时才会进行动态链接,也就是说,Class文件中不会保存各个方法和字段的最终内存布局信息
    • 因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时, 需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中.
    • 这里说明下符号引用和直接引用的区别与关联:
    • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
    • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。
    总结1:
    • 这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1类型的标志位(tag),代表当前这个常量项使用的是哪种常量类型。
    • 在常量池列表中,CONSTANT_utf8_info常量项是一种使用改进过的UTF-8编码格式来存储诸如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符串信息。
    • 这14种常量项结构还有一个特点是,其中13个常量项占用的字节固定,只有CONSTANT_utf8_info(字符串)占用字节不固定,其大小由length决定。为什么呢?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,大小不固定,编译后,通过utf-8编码,就可以知道其长度。
    总结2:
    • 常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。
    • 常量池中为什么要包含这些内容:Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态链接的内容,在虚拟机类加载过程时再进行详细讲解

    4、访问标识/标记

    在常量池后,紧跟着访问标记。该标记使用两个字节表示一用于识别一些类或者接口层次的访问信息,包括:这个 Class是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final等。

    各种访问标记如下所示:
    在这里插入图片描述

    需要注意的是,这里的两个字节,代表的是上表中各标志对应标志值的和

    如:

    • 这两个字节对应的是0X0021,则意味着是 PUBLIC+SUPER 。

    补充说明:

    • ACC_INTERFACE标志:带有ACC_INTERFACE标志的Class文件表示的是接口而不是类,反之则表示的是类而不是接口。
    • 如果一个Class文件被设置了 ACC_INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志。同时它不能再设置 ACC_FINAL、ACC_SUPER 或 ACC_ENUM 标志。
    • 如果没有设置ACC_INTERFACE标志,那么这个Class文件可以具有上表中除 ACC_ANNOTATION外的其他所有标志。当然,ACC_FINAL和ACC_ABSTRACT这类互斥的标志除外。这两个标志不得同时设置。
    • ACC SUPER标志:用于确定类或接口里面的invokespecial指令使用的是哪一种执行语义。针对Java虚拟机指令集的编译器都应当设置这个标志。对于Java SE 8及后绿版本来说,无论Class文件中这个标志的实际值是什么,也不管Class文件的版本号是多少,Java虚拟机都认为每个Class文件均设置了ACC_SUPER标志。
    • ACC_SUPER标志是为了向后兼容由旧Java编译器所编译的代码而设计的。目前的 ACC_SUPER标志在由JDK 1.0.2之前的编译器所生成的access_flags中是没有确定含义的,如果设置了该标志,那么oracle的Java虚拟机实现会将其忽略。
    • ACC_SYNTHETIC标志:意味着该类或接口是由编译器生成的,而不是由源代码生成的。
    • ACC_ANNOTATION标志:注解类型必须设置ACC_ANNOTATION标志。如果设置了 ACC_ANNOTATION标志,那么也必须设置ACC_INTERFACE标志。
    • ACC_ENUM标志:表明该类或其父类为举类型。

    5、类索引、父类索引、接口索引

    在这里插入图片描述

    这三项数据来确定这个类的继承关系

    • 类索引:用于确定这个类的全限定名
    • 父类索引:用于确定这个类的父类的全限定名。由于 Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object 之外,所有的Java类都有父类,因此除了java.lang.Object 外,所有Java类的父类索引都不为 0。
    • 接口索引集合:就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends 语句)后的接口顺序从左到右排列在接口索引集合中。
    • 因为接口索引是个数组,所以必定会有字节来表示数组长度

    (1)this_class(类索引)

    • 2字节无符号整数,指向常量池的索引。
    • 它提供了类的全限定名,如com/CSDN/java/Demo。this_class的值必须是对常量池表中某项的一个有效索引值。
    • 常量池在这个索引处的成员必须为CONSTANT_Class_Info类型结构体,该结构体表示这个Class文件所定义的类或接口。

    (2)super_class (父类索引)

    • 2字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名
    • 如果我们没有继承任何类,其默认继承的是java/lang/0bject类。
    • 同时,由于Java不支持多继承,所以其父类只有一个。super.class指向的父类不能是final。

    (3)interfaces

    • 指向常量池索引集合,它提供了一个符号引用到所有已实现的接口
    • 由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class(当然这里就必须是接口,而不是类)。
    • 顺序从左到右排列在接口索引集合中。A implement B, C。则B在[0],C在[1]

    6、字段表(Fields)集合

    (1)概述

    • 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。
    • 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。.
    • 它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static)、是否是常量(final修饰符)等。

    注意事项:

    • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
      在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

    (2)fields count (字段计数器)

    • fields count的值表示当前class文件fields表的成员个数。使用两个字节来表示。fields表中每个成员都是一个field info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

    (3)fields [ ] (字段表)

    • fields表中的每个成员都必须是一个fields info结构的数据项,用于表示当前类或接口中某个字段的完整描述
    • 一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。

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

    (4)字段表结构

    字段表作为一个表,同样有他自己的结构:
    在这里插入图片描述

    1)访问标志:

    作用域修饰符(public、private、protected)、static修饰符、final修饰符、volatile修饰符等等。因此,其可像类的访问标志那样,使用一些标志来标记字段。

    需要注意的是,这里的两个字节,代表的是上表中各标志对应标志值的和

    如:这两个字节对应的是0X0021,则意味着是 PUBLIC+SUPER 。

    字段的访问标志有如下这些:
    在这里插入图片描述

    2) 字段名索引
    • 不是具体值,是索引!!!
    • 上面常量池讲过了!!!add()方法和num字段的简单名称分别是add和num。
    • 根据字段名索引的值,查询常量池中的指定索引项即可。
    • 即,如果这里是0X0005,则代表的是上面所讲的常量池中的第五个值。
    3) 描述符索引
    • 不是具体值,是索引!!!
    • 描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示。
    • 这里可以参照上方的符号引用来理解
    • 索引指的就是指向常量池中的对应位置的值

    6、方发表集合

    (1)概述

    methods:指向常量池索引集合,它完整描述了每个方法的签名。

    • 在字节码文件中,每一个method info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等。
    • 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来。
    • 一方面,methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
    • 另一方面,methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法()和实例初始化方法())。

    使用注意事项:

    • 在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名
    • 特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。
    • 但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中。
    • 也就是说,尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。

    (2)方法计数器(methods count)

    • methods count的值表示当前Class文件methods表的成员个数。使用两个字节来表示。
    • methods 表中每个成员都是一个method_Info结构。

    (3)方法表(methods[ ] )

    methods表中的每个成员都必须是一个method_Info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_Info结构的access flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令

    (4)方法表结构

    • method_Info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法。
    • 方法表的结构实际跟字段表是一样的
    • 方法表结构如下:
      在这里插入图片描述
    1)访问标志:

    和字段表一样,方发表也有访问标志,部分和字段访问标志一样,部分则不同。

    需要注意的是,这里的两个字节,代表的是上表中各标志对应标志值的和

    如:这两个字节对应的是0X0021,则意味着是 PUBLIC+SUPER 。

    方法的访问标志有如下这些:
    在这里插入图片描述

    2)方法名索引
    • 不是具体值,是索引!!!
    • 上面常量池讲过了!!!add()方法和num字段的简单名称分别是add和num。
    • 根据方法名索引的值,查询常量池中的指定索引项即可。
    • 即,如果这里是0X0005,则代表的是上面所讲的常量池中的第五个值。
    3)描述符索引
    • 不是具体值,是索引!!!
    • 描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示。
    • 这里可以参照上方的符号引用来理解
    • 索引指的就是指向常量池中的对应位置的值
    int abc(int[] x, int y)
    // 方法名就是abc
    // 描述符为:([II)I
    

    7、属性表(该小结为AI生成,请注意甄别。错了我不负责哦~)

    在Java虚拟机(JVM)的ClassFile结构中,字段的属性表和方法的属性表分别描述了字段和方法的附加信息。这些属性表可以包含各种额外的数据,例如注释、调试信息、性能优化信息等。

    字段的属性表(Field Attributes)

    字段的属性表是在ClassFile的字段结构中包含的一系列属性。每个字段结构都可以包含零个或多个属性。这些属性用于提供与字段相关的额外信息。常见的字段属性有:

    1. ConstantValue:这个属性用于表示常量字段的初始值。对于static final字段,如果它是一个基本类型或String类型的常量,它的初始值会在这个属性中指定。

    2. Synthetic:标记一个字段为编译器生成,而不是在源代码中显式定义。

    3. Deprecated:标记一个字段为不推荐使用。

    方法的属性表(Method Attributes)

    方法的属性表是在ClassFile的方法结构中包含的一系列属性。每个方法结构同样可以包含零个或多个属性。这些属性用于提供与方法相关的附加信息。常见的方法属性有:

    1. Code:这个属性包含了方法的字节码,以及其他与方法实现相关的信息,如局部变量表和异常处理表。

    2. Exceptions:列出方法抛出的异常类型。

    3. LineNumberTable:这个属性用于调试目的,映射方法的字节码指令到源代码的行号。

    4. LocalVariableTable:这个属性也是用于调试目的,描述方法的局部变量及其作用域。

    5. Synthetic:标记一个方法为编译器生成,而不是在源代码中显式定义。

    6. Deprecated:标记一个方法为不推荐使用。

    7. RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations:用于表示方法的注解。

    作用

    字段的属性表和方法的属性表在Java应用中有重要作用:

    1. 提供元数据:属性表包含了大量的元数据,使得Java编译器、虚拟机和开发工具能够理解和操作类的结构。

    2. 支持调试和错误诊断:调试信息(如行号表和局部变量表)使得开发者可以在调试器中查看源代码对应的位置和变量状态,从而更容易定位和修复问题。

    3. 性能优化:某些属性,如Synthetic,帮助编译器和虚拟机进行性能优化,因为它们能够识别编译器生成的代码。

    4. 注解处理:属性表中的注解信息可以在运行时通过反射机制读取,从而支持框架和库的注解处理功能。

    总的来说,字段和方法的属性表在Java ClassFile结构中提供了灵活和扩展性,允许在字节码级别上附加各种有用的信息,以支持不同的编译器和运行时需求。

  • 相关阅读:
    Java程序员毕业N年系列----毕业二年
    Java内存模型:创建对象在堆区如何分配内存
    NLP实践——Few-shot事件抽取《Building an Event Extractor with Only a Few Examples》
    机器学习算法三之Python机器学习库sklearn简介
    手游帧率不稳定的原因是什么?
    滤波算法_扩展卡尔曼滤波(EKF, Extended Kalman filter)_全网最详细的数学推导_Part2
    GBASE 8s 数据库的恢复
    [ vulhub漏洞复现篇 ] Django debug page XSS漏洞 CVE-2017-12794
    .NET ABP.Zero 项目疑似内存排查历程
    【玩玩Vue】使用elementui页面布局和控制页面的滚动
  • 原文地址:https://blog.csdn.net/qq_38096989/article/details/139453638