字节码文件概述

字节码文件的跨平台性

Java语言:跨平台的语言

  • 当Java源代码胜利编译字节码后,如果想在不同的平台下面运行,则无需再次编译
  • 这个劣势目前来说曾经不再吸引人,因为Python、PHP、Ruby、Lisp等有弱小的解释器
  • 跨平台曾经快成为一门语言的必选个性

Java虚拟机:跨语言的平台

Java虚拟机不和包含Java在内的任何语言绑定,它只与Class文件这种特定的二进制文件所关联,无论应用何种语言进行软件开发,只有能将源文件编译为正确的Class文件,那么这种语言就能够这Java虚拟机上执行。

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

  • 前端编译器的次要工作就是负责将合乎Java语法标准的Java代码转换为合乎JVM标准的字节码文件
  • javac是一种可能将Java源码编译为字节码的前端编译器
  • javac编译器这将Java源码编译为一个无效的字节码文件过程中经验了4个步骤,别离是词法解析、语法解析、语义解析以及生成字节码

Java的编译

  1. 前端编译

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

    长处:

    1. 许多Java语法新个性(泛型、外部类等),是靠前端编译器实现的,而不是依赖虚拟机。
    2. 编译成的Class文件能够间接给JVM解释器解释执行,省去编译工夫,放慢启动速度。

毛病:

    1. 对代码运行效率简直没有任何优化措施。
    2. 解释执行效率较低,所以须要联合上面的JIT编译。
    1. 后端编译/JIT编译

      通过Java虚拟机(JVM)内置的即时编译器(Just In Time Compiler,JIT编译器);在运行时把Class文件字节码编译成本地机器码的过程。

      长处:

      1. 通过在运行时收集监控信息,把"热点代码"(Hot Spot Code)编译成与本地平台相干的机器码,并进行各种档次的优化。
      2. 能够大大提高执行效率。

    毛病:

    1. 收集监控信息影响程序运行。
    2. 编译过程占用程序运行工夫。
    3. 编译机器码占用内存。
    1. 动态提前编译(AOT)

      程序运行前,间接把Java源码文件编译成本地机器码的过程。

      长处:

      1. 编译不占用运行工夫,能够做一些较耗时的优化,并可放慢程序启动。
      2. 把编译的本地机器码保留磁盘,不占用内存,并可屡次应用。

    毛病:

    1. 因为Java语言的动态性(如反射)带来了额定的复杂性,影响了动态编译代码的品质,个别动态编译不如JIT编译的品质,这种形式用得比拟少。

    目前Java体系中次要还是采纳前端编译+JIT编译的形式

    运作过程:

    1. 首先通过前端编译把合乎Java语言标准的程序代码转化为满足JVM标准所要求Class格局。
    2. 而后程序启动时Class格式文件发挥作用,解释执行,省去编译工夫,放慢启动速度。
    3. 针对Class解释执行效率低的问题,在运行中收集性能监控信息,得悉"热点代码"。
    4. JIT逐步发挥作用,把越来越多的热点代码"编译优化老本地代码,进步执行效率。

    透过字节码指令看代码细节

    面试题

    1. 类文件构造有几个局部?
    2. 晓得字节码吗?字节码都有哪些?Integer x = 5; int y = 5; 比拟 x == y 都经验哪些步骤?

    首先在申明Integer x的时候会调用Integer.valueOf办法,首先看下这个办法

    public static Integer valueOf(int i) {        if (i >= IntegerCache.low && i <= IntegerCache.high)            return IntegerCache.cache[i + (-IntegerCache.low)];        return new Integer(i);}

    如果所传入的值是-128~127之间,就只用动态外部类的cache数组,否则就new一个新的对象。

    这也表明为什么 Integer x = 128; Integer y = 128; x != y 的起因。

    之后能够看到调用了 Integer.intValue 进行主动拆箱操作,使得 x 变成 int 类型进行比拟。

    虚拟机的基石:Class文件

    • 字节码文件里是什么?

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

    • 什么是字节码指令?

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

      比方 aload_1 (操作码) 、aload 4 (操作码 + 操作数),因为aload只有0、1、2、3所以如果想持续裁减,就要用到操作数。

    Class文件构造

    • 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文件版本
      • 常量池
      • 拜访标记
      • 类索引,父类索引,接口索引汇合
      • 字段表汇合
      • 办法表汇合
      • 属性表汇合

    | 类型 | 名称 | 阐明 | 长度 | 数量 |
    | -------------- | ------------------- | ---------------------- | ------- | --------------------- |
    | u4 | magic | 魔数,辨认Class文件格式 | 4个字节 | 1 |
    | u2 | minor_version | 副版本号(小版本) | 2个字节 | 1 |
    | u2 | major_version | 主版本号(大版本) | 2个字节 | 1 |
    | u2 | constant_pool_count | 常量池计数器 | 2个字节 | 1 |
    | cp_info | constant_pool | 常量池表 | n个字节 | constant_pool_count-1 |
    | u2 | access_flags | 拜访标识 | 2个字节 | 1 |
    | u2 | this_class | 类索引 | 2个字节 | 1 |
    | u2 | super_class | 父类索引 | 2个字节 | 1 |
    | u2 | interfaces_count | 接口计数器 | 2个字节 | 1 |
    | u2 | interfaces | 接口索引汇合 | 2个字节 | interfaces_count |
    | u2 | fields_count | 字段计数器 | 2个字节 | 1 |
    | field_info | fields | 字段表 | n个字节 | fields_count |
    | u2 | methods_count | 办法计数器 | 2个字节 | 1 |
    | method_info | methods | 办法表 | n个字节 | methods_count |
    | u2 | attributes_count | 属性计数器 | 2个字节 | 1 |
    | attribute_info | attributes | 属性表 | n个字节 | attributes_count |

    字节码文件解析

    首先咱们创立一个简略的源码:

    public class Demo {    private int num = 1;    public int add() {        num = num + 2;        return num;    }}

    通过 javac 命令编译后能够看到Class文件内容:

    public class Demo {    private int num = 1;    public Demo() {    }    public int add() {        this.num += 2;        return this.num;    }}

    为什么编译当前减少了无参结构器,以及this关键字,咱们一点点剖析。

    之后咱们应用Notepad++,须要装置一个HEX-Editor插件来关上这个Class文件就能够看到下图内容。

    这里就间接应用 excel 来清晰解释具体内容。

    魔数:Class文件的标记

    • 每个Class文件结尾的4个字节的无符号整数成为魔数(Magic Number)
    • 它的惟一作用就是确定这个文件是否为一个能被虚拟机承受的无效非法的Class文件。即:魔数是Class文件的标识符。
    • 魔数值固定为0xCAFEBABE
    • 如果一个Class文件不以0xCAFEBABE结尾,虚拟机在进行文件校验的时候就会间接抛出ClassFormatError谬误。
    • 应用魔数而不是扩展名来辨认次要是基于平安方面思考,因为文件扩展名是能够随便扭转的。

      Class文件版本号

    • 紧接着魔数的 4 个字节是 Class文件的版本号。同样也是4个字节。第5个和第6个字节所代表的的含意就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version。
    • 它们独特形成了Class文件的格局版本号。譬如某个Class文件的主版本号为M,副版本号为m,那么这个Class文件的格局版本号就是 M.m。
    • 版本号和Java编译器的对应关系表如下:

      | 主版本(十进制) | 副版本(十进制) | 编译器版本 |
      | ---------------- | ---------------- | ---------- |
      | 45 | 3 | 1.1 |
      | 46 | 0 | 1.2 |
      | 47 | 0 | 1.3 |
      | 48 | 0 | 1.4 |
      | 49 | 0 | 1.5 |
      | 50 | 0 | 1.6 |
      | 51 | 0 | 1.7 |
      | 52 | 0 | 1.8 |
      | 53 | 0 | 1.9 |
      | 54 | 0 | 1.10 |
      | 55 | 0 | 1.11 |

    • Java的版本号是从45开始的,JDK 1.1 之后的每个JDK大版本公布主版本号向上加1。
    • 不同版本的Java编译器编译的Class文件对应的版本是不一样的。目前,高版本的Java虚拟机能够执行低版本编译器编译的Class文件,然而低版本的Java虚拟机不能执行由高版本编译器生成的Class文件。否则JVM会抛出java.lang.UnsupportedClassVersionError异样。

    常量池:寄存所有常量

    • 常量池是Class文件中内容最为丰盛的区域之一。常量池对于Class文件中的字段和办法解析有着至关重要的作用。
    • 随着Java虚拟机的一直倒退,常量池的内容也日渐丰盛。能够说,常量池是整个Class文件的基石。
    • 在版本号之后,紧跟着的就是常量池的数量,以及若干个常量池表项。
    • 常量池中常量的数量是不固定的,所以须要一个u2类型的无符号数,代表着常量池容量计数器(constant_pool_count),与Java语言习惯不一样的是,容量计数器是从1开始而不是0。
    • 常量池表项中,用于寄存编译期生成的各种字面量符号援用,这部分内容将在类加载后进入办法区的运行时常量池中寄存。

    常量池计数器

    • 因为常量池的数量不固定,所以须要搁置两个字节来示意常量池容量计数器。
    • 常量池计数器(u2类型):从1开始,示意常量池中有多少项常量。即constant_pool_count = 1示意常量池中有0个常量池。
    • 咱们看方才举例的Demo:其值为0x0016,转换为十进制也就是22。

      然而理论中只有21项常量,范畴是1-21。

      这里的常量池把第0项空进去了,为了满足前面某些指向常量池的索引值的数据在特定状况下须要表白"不援用任何一个常量池项"的含意,这种状况能够应用索引0来示意。

    常量池表

    • constant_pool是一种表及构造,以1 ~ constant_pool_count - 1 为索引。表明前面有多少个常量项。
    • 常量池次要寄存放两大类变量:字面量(Literal)符号援用(Symbolic Reference)
    • 它蕴含了Class文件构造及其子结构中援用的所有字符串常量、类、或接口名、字段名、和其它常量。常量池中的每一项都具备雷同特色。第1个字节作为类型标记,用于确定该项的格局,这个字节成为tag byte (标记字节)。
    字面量和符号援用

    常量池次要寄存放两大类变量:字面量(Literal)符号援用(Symbolic Reference)

    常量具体的常量
    字面量文本字符串
    申明为final的常量值
    符号援用类和接口的全限定名
    字段和名称的描述符
    办法的名称和描述符

    全限定名

    com/test/Demo这个就是类的全限定名,仅仅是把包名的"."替换成了"/",为了使间断的多个全限定名之间不产生混同,在应用时最初个别会退出一个";"示意全限定名完结。

    简略名称

    简略名称是指没有类型和参数润饰的办法或者字段名称,下面例子中的类的add()办法,和num字段的简略名称别离是add和num。

    描述符

    描述符的作用是用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值。依据描述符规定,根本数据类型(boolean,byte,char,short,int,float,long,double)以及代表无返回值的void类型都用一个大写字符来示意,而对象类型则用字符L加对象的全限定名来示意:

    标志符含意
    B根本数据类型byte
    C根本数据类型char
    D根本数据类型double
    F根本数据类型float
    I根本数据类型int
    J根本数据类型long
    S根本数据类型short
    Z根本数据类型boolean
    V代表void类型
    L对象类型,比方:Ljava/lang/Object;
    [数组类型,代表一维数组。比方:double[][][] = [[[D
    public static void main(String[] args) {        Object[] arr = new Object[10];        System.out.println(arr);//[Ljava.lang.Object;@14ae5a5        Long[][] longs = new Long[10][10];        System.out.println(longs);//[[Ljava.lang.Long;@7f31245a                int[][] ints = new int[10][10];        System.out.println(ints);//[[I@7f31245a}

    须要留神的是,用描述符来形容办法的时候,先参数列表后返回值的程序形容,参数列表依照参数的严格程序放在一组小括号"()"内。

    虚拟机在加载Class文件时才会进行动静链接,也就是说,Class文件中不会保留各个办法和字段的最终内存布局信息,因而,这些字段和办法的符号援用不通过转换是无奈间接被虚拟机应用的。当虚拟机运行时,须要从常量池中取得对应的符号援用,再在类加载过程中的解析阶段将其替换为间接援用,并翻译到具体的内存地址中

    • 符号援用:符号援用以一组符号来形容所援用的指标,符号能够是任何模式的字面量,只有应用时无歧义地定位到指标即可。符号援用与虚拟机实现的内存布局无关,援用的指标并不一定曾经加载到了内存中。
    • 间接援用:间接援用能够是间接指向指标的指针、绝对偏移量或是一个能间接定位到指标的句柄。间接援用是与虚拟机实现的内存布局相干的,同一个符号援用在不同虚拟机实例上翻译进去个别不会雷同。如果有了间接援用,则阐明援用的指标必然存在内存之中。
    常量类型和构造
    类型标记(或标识)形容
    CONSTANT_utf8_info1UTF-8编码的字符串
    CONSTANT_Integer_info3整型字面量
    CONSTANT_Float_info4浮点型字面量
    CONSTANT_Long_info5长整型字面量
    CONSTANT_Double_info6双精度浮点型字面量
    CONSTANT_Class_info7类或接口的符号援用
    CONSTANT_String_info8字符串类型字面量
    CONSTANT_Fieldref_info9字段的符号援用
    CONSTANT_Methodref_info10类中办法的符号援用
    CONSTANT_InterfaceMethodref_info11接口中办法的符号援用
    CONSTANT_NameAndType_info12字段或办法的符号援用
    CONSTANT_MethodHandle_info15示意办法句柄
    CONSTANT_MethodType_info16标记办法类型
    CONSTANT_InvokeDynamic_info18示意一个动静办法调用点

    从下面的图中能够看到,尽管每一项的构造都不雷同,然而它们有个共同点,就是每一项的第一个字节都是一个标记位,标识这一项是哪种类型的常量。

    拜访标记(access_flag)

    • 在常量池后,紧接着拜访标记,该标记应用两个字节示意,用于辨认一些类或者接口档次的访问信息,包含:这个Class是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被申明为 final 等,具体阐明如下:

      标记名称标记值含意
      ACC_PUBLIC0x0001标记为public类型
      ACC_FINAL0x0010标记被申明为final,只有类能够设置
      ACC_SUPER0x0020标记容许应用invokespecial字节码指令的新语义,JDK1.0.2之后编译进去的类的这个标记默认为真。(应用加强的办法调用父类办法)
      ACC_INTERFACE0x0200标记这是一个接口
      ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标记值为真,其余类型为假
      ACC_SYNTHETIC0x1000标记此类并非由用户代码产生(即:由编译器产生的类,没有源码对应)
      ACC_ANNOTATION0x2000标记这是一个注解
      ACC_ENUM0x4000标记这是一个枚举
    • 类的拜访权限通常为 ACC_ 结尾的常量。
    • 每一种类型的示意都是通过设置拜访标记的32位中的特定位来实现的,比方如果是public final的类,则标记为 ACC_PUBLIC | ACC_FINAL。
    • 应用ACC_SUPER能够让类更精确的定位到父类的办法super.method(),默认都是设置并应用这个标记。

    咱们能够看到下面的Demo的字节码对应的拜访标记是21,也就是对应表格中的 ACC_PUBLIC 和 ACC_SUPER 加起来就等于21。

    类索引、父类索引、接口索引汇合

    长度含意
    u2this_class
    u2super_class
    u2interfaces_count
    u2interfaces[interfaces_count]
    • 类索引用于确定这个类的继承关系。
    • 父类索引用于确定这个类的父类全限定名,因为Java语言不容许多重继承,所以父类索引只有一个,除了Object 之外,所有的Java类都有父类,因而除了Object之外,所有Java类的父类索引都不为0。
    • 接口索引汇合就用来形容这个类实现了拿些接口,这些被实现的接口将按implements语句(如果以后类自身是一个接口,则该当是 extends 语句)后的接口程序从左到右排列在接口索引汇合中。

    this_class(类索引)

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

    super_class(父类索引)

    • 2字节无符号整数,指向常量池的索引。它提供了以后类的父类全限定名。如果咱们没有继承任何类,其默认继承的是Java/lang/Object类。同时,因为Java不反对多继承,所以其父类只有一个。
    • super_class指向的父类不能是final类型。

    interfaces

    • 指向常量池索引汇合,它提供了一个符号援用到所有已实现的接口。
    • 因为一个类能够实现多个接口,因而须要以数组模式保留多个接口的索引,示意接口的每个索引也是一个指向常量池的Constant_Class(指向的是接口,并不是类)。

    interfaces_count(接口计数器)

    interfaces_count 项的值示意以后类或者接口的间接超接口数量。

    interfaces[](接口索引汇合)

    interfaces[]中每个成员的值必须是对常量池表中某项索引的无效索引值,它的长度为interfaces_count。每个成员interfaces[i]必须为constant_class_info构造,其中 0 <= i < interfaces_count。在interfaces[]中,各成员所示意的接口程序对应的源代码中给定的接口程序(从左至右)一样,即 interface[0]对应的是源代码中最右边的接口。

    字段表汇合

    • 用于形容接口或类中申明的变量。字段(field)包含类级变量以及实例变量,然而不包含办法外部、代码块外部申明的局部变量。
    • 字段叫什么名字,字段被定义为什么数据类型,这些都是无奈固定的。只能援用常量池中的常量来形容。
    • 它指向常量池索引汇合,它形容了每个字段的残缺信息。比方字段的标识符、拜访修饰符(public、private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。

    留神:

    • 字段表汇合中不会列出从父类或者实现的接口中继承来的字段,但有可能列出本来Java代码之中不存在的字段。譬如在内部类中为了放弃对外部类的拜访性,会主动增加指向外部类实例的字段。
    • 在Java语言中字段是无奈重载的,两个字段的数据类型,修饰符不论是否雷同,都必须应用不一样的名称,然而对于字节码来说,如果两个字段的描述符不统一,那字段名重名也是非法的。

    fields_count(字段计数器)

    fields_count的值示意以后Class文件fields表的成员个数,用2个字节示意。

    fields表中每个成员都是一个field_info构造,用于示意该类或接口所申明的所有字段或者实例字段,不包含办法外部申明的变量,也不包含从父类或父接口继承的那些字段。

    fields[](字段表)

    • fields表中的每个成员都必须是一个field_info构造的数据项,用于示意以后类或接口中某个字段的残缺形容。
    • 一个字段的信息包含如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。

      • 作用域
      • 实例变量还是类变量
      • 是否final
      • 是否volatile
      • 是否序列化 transient 润饰
      • 字段数据类型(根本数据类型,对象,数组)
      • 字段名称
    • 字段表构造
    类型名称含意数量
    u2access_flags拜访标记1
    u2name_index字段名索引1
    u2descriptor_index描述符索引1
    u2attributes_count属性计数器1
    attribute_infoattributes属性汇合attributes_count

    字段表拜访标识

    咱们晓得,一个字段能够被各种关键字润饰,比方作用域修饰符、static修饰符、final修饰符、volatile修饰符等等。字段拜访标记有如下这些:

    标记名称标记值含意
    ACC_PUBLIC0x0001字段是否为public
    ACC_PRIVATE0x0002字段是否为private
    ACC_PROTECTED0x0004字段是否为protected
    ACC_STATIC0x0008字段是否为static
    ACC_FINAL0x0010字段是否为final
    ACC_VOLATILE0x0040字段是否为volatile
    ACC_TRANSTENT0x0080字段是否为transient
    ACC_SYNCHETIC0x1000字段是否为由编译器主动产生
    ACC_ENUM0x4000字段是否为enum

    字段名索引

    依据字段名索引的值,查问常量池中的指定索引项即可。

    描述符索引

    描述符的作用是用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值。依据描述符规定,根本数据类型及无返回值的void都是用大写符来示意,对象应用字符L+全限定名示意。

    属性汇合

    一个字段还可能领有一些属性,用于存储更多的额定信息。比方初始化值、一些正文信息等。属性个数寄存在attribute_count中,属性具体内容存在attributes数组中。

    构造为:

    ConstantValue_attribute{    u2 attribute_name_index;    u4 attribute_length;    u2 constantvalue_index;}

    对于常量属性而言,attribute_length的值恒为2。

    常量值索引所指向的 #8 其实就是对应int的值。

    办法表汇合

    methods:指向常量池索引汇合,它残缺形容了每个办法的签名。

    • 在字节码文件中,每一个method_info项都对应着一个类或者接口中的办法信息。比方办法的拜访修饰符,办法的返回值类型,以及办法的参数信息等。
    • 如果办法不是形象的或者不是native的,那么字节码就会体现进去。
    • 一方面,methods表只形容以后类或接口中申明的办法,不包含从父类或父接口继承的办法。另一方面,methods表有可能会呈现由编译器主动增加的办法,最典型的就是编译器产生的办法信息,比方类或者接口初始化办法<clinit>()和实例初始化办法<init>()

    methodds_count(办法计数器)

    methods_count的值示意以后class文件methods表的成员个数。应用 2 个字节来示意。

    methods表中每个成员都是一个method_info构造。

    methods[](办法表)

    • methods表中的每个成员都必须是一个method_info构造,用于示意以后类或接口中某个办法的残缺形容。如果某个method_info构造的access_flags项既没有设置ACC_NATIVE标记和ACC_ABSTRACT标记,那么该构造中也应该蕴含实现这个办法所用到的Java虚拟机指令。
    • method_info构造能够示意类和接口中定义的所有办法,包含实例办法、类办法、实例初始化办法和类或接口初始化办法。
    • 办法表的构造理论和字段表是统一的。如下:
    类型名称含意数量
    u2access_flags拜访标记1
    u2name_index字段名索引1
    u2descriptor_index描述符索引1
    u2attributes_count属性计数器1
    attribute_infoattributes属性汇合attributes_count

    属性表汇合

    办法表汇合之后的属性表汇合,指的是class文件所携带的辅助信息,比方该Class文件的源文件的名称,以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试。

    此外,字段表、办法表都能够有本人的属性表。用于形容某些场景专有的信息。

    属性表汇合的限度没有那么严格,不再要求各个属性表具备严格的程序,并且只有不与已有的属性名反复,任何人实现的编译器都能够向属性表中写入本人定义的属性信息,但Java虚拟机运行时会疏忽掉它不意识的属性。

    属性的通用格局

    属性表的构造比拟灵便,各种不同的属性只有满足以下构造即可:

    类型名称数量含意
    u2attribute_name_index1属性名索引
    u4attribute_length1属性长度
    u1infoattribute_length属性表

    属性类型

    属性名称应用地位含意
    Code办法表Java代码编译成的字节码指令
    ConstantValue字段表final关键字定义的常量池
    Deprecated类,办法,字段表被申明为deprecated的办法和字段
    Exceptions办法表办法抛出的异样
    EnclosingMethod类文件仅当一个类为部分类或者匿名类是能力领有这个属性,这个属性用于标识这个类所在的外围办法
    InnerClass类文件外部类列表
    LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
    LocalVariableTableCode属性办法的局部变量形容
    StackMapTableCode属性JDK1.6中新增的属性,供新的类型查看测验器检查和解决指标办法的局部变量和操作数有所须要的类是否匹配
    Signature类,办法表,字段表用于反对泛型状况下的办法签名
    SourceFile类文件记录源文件名称
    SourceDebugExtension类文件用于存储额定的调试信息
    Synthetic类,办法表,字段表标记办法或字段为编译器主动生成的
    LocalVariableTypeTable应用特色签名代替描述符,是为了引入泛型语法之后能形容泛型参数化类型而增加
    RuntimeVisibleAnnotations类,办法表,字段表为动静注解提供反对
    RuntimeInvisibleAnnotations表,办法表,字段表用于指明哪些注解是运行时不可见的
    RuntimeVisibleParameterAnnotation办法表作用与RuntimeVisibleAnnotations属性相似,只不过作用对象为办法
    RuntimeInvisibleParameterAnnotation办法表作用与RuntimeInvisibleAnnotations属性相似,作用对象哪个为办法参数
    AnnotationDefault办法表用于记录注解类元素的默认值
    BootstrapMethods类文件用于保留invokeddynamic指令援用的疏导形式限定符

    Code属性

    Code属性就是寄存办法体外面的代码,像接口或者形象办法,没有具体的办法体,因而也就不会有Code属性了。

    Code属性表的构造:

    类型名称数量含意
    u2attribute_name_index1属性名索引
    u4attribute_length1属性长度
    u2max_stack1操作数栈深度的最大值
    u2max_locals1局部变量表所需的存续空间
    u4code_length1字节码指令的长度
    u1codecode_length存储字节码指令
    u2exception_table_length1异样表长度
    exception_infoexception_tableexception_length异样表
    u2attributes_count1属性汇合计数器
    attribute_infoattributesattributes_count属性汇合

    LineNumberTable属性

    • LineNumberTable属性是可选变长属性,位于Code构造的属性表
    • LineNumberTable属性是用来形容Java源码行号字节码行号之间的对应关系。
    • start_pc,即字节码行号;line_number,即Java源代码的行号
    • 在Code属性的属性表中,LineNumberTable属性能够依照任意程序呈现,此外,多个LineNumberTable属性能够独特示意一个行号在源文件中示意的内容,即LineNumberTable属性不须要与源文件的行一一对应。

    LineNumberTable属性表构造

    LineNumberTable_attribute {     u2 attribute_name_index;     u4 attribute_length;     u2 line_number_table_length;     {   u2 start_pc;         u2 line_number;     } line_number_table[line_number_table_length];  } 
    类型名称数量含意
    u2attribute_name_index1属性名索引
    u4attribute_length1属性长度
    u2line_number_table_length1行号表长度
    line_number_infoline_number_tableline_number_table_length行号表

    SourceFile属性

    类型名称数量含意
    u2attribute_name_index1属性名索引
    u4attribute_length1属性长度
    u2sourcefile_index1源码文件索引

    其长度总是固定的8个字节。

    咱们看之前的Excel最初一位也能够看到对应的就是源文件名。