JVM系列(二)Class字节码探索

文章目录

  • 前言
  • 1.下载&调试javaC源码
    • 1.1.官网下载源码
    • 1.2.本地创建一个java工程
    • 1.3.执行
    • 1.4.断点运行
    • 1.5其他方式查看javac文件
    • 1.6.jclasslib
  • 2.class文件结构
    • 2.1.class文件整体长啥样
    • 2.2.魔数
    • 2.3.版本号
    • 2.4.常量池
      • 2.4.1.tag常量项类型
      • 2.4.2.info信息
        • 2.4.2.1.CONSTANT_Methodref_info
        • 2.4.2.2.CONSTANT_Fieldref_info
        • 2.4.2.3.CONSTANT_String_info
        • 2.4.2.4.其他数据
        • 2.4.2.5.CONSTANT_Class_info
        • 2.4.2.6.CONSTANT_Utf8_info
        • 2.4.2.7.CONSTANT_NameAndType_info
        • 2.4.2.8.CONSTANT_Integer_info
        • 2.4.2.9.CONSTANT_Float_info
        • 2.4.2.10.CONSTANT_Long_info
        • 2.4.2.11.CONSTANT_Double_info
        • 2.4.2.12.CONSTANT_MethodHandle_info
        • 2.4.2.13.CONSTANT_MethodType_info
        • 2.4.2.14.CONSTANT_InvokeDynamic_info
        • 2.4.2.15.reference_kind类型
    • 2.5.类访问标记
      • 2.5.1.类access_flags类型表
      • 2.5.2.内部类access_flags类型表
    • 2.6.超类索引&接口表索引
    • 2.7.字段表
      • 2.7.1.字段access_flags类型表
      • 2.7.2.字段描述符
      • 2.7.3.字段属性
    • 2.8.方法表
      • 2.8.1方法access_flags类型表
      • 2.8.2.方法名&方法描述符
      • 2.8.3.方法属性
    • 2.9.属性表
      • 2.9.1预定义属性
      • 2.9.2.ConstantValue
        • 2.9.2.1.constantvalue_index
      • 2.9.3.Code
      • 2.9.4.exception_info
      • 2.9.5.StackMapTable
      • 2.9.6.Exceptions
      • 2.9.7.InnerClasses
        • 2.9.7.1.inner_classes_info
      • 2.9.8.EnclosingMethod
      • 2.9.9.Synthetic
      • 2.9.10.Signature
      • 2.9.11.SourceFile
      • 2.9.12.SourceDebugExtension
      • 2.9.13.LineNumberTable
        • 2.9.13.1.line_number_info
      • 2.9.14.LocalVariableTable
        • 2.9.14.1.local_variable_info
      • 2.9.15.LocalVariableTypeTable
        • 2.9.15.1.local_variable_type_info
      • 2.9.16.Deprecated
      • 2.9.17.RuntimeVisibleAnnotations
      • 2.9.18.annotation
        • 2.9.18.1.element_value_pairs_info
        • 2.9.18.2element_value
        • 2.9.18.3.value_info
        • 2.9.18.4.const_value_index_info
        • 2.9.18.5array_value_info
      • 2.9.19.RuntimeInvisibleAnnotations
      • 2.9.20.RuntimeVisibleParameterAnnotations
        • 2.9.20.1.parameter_annotations_info
      • 2.9.21.RuntimeInvisibleParameterAnnotations
      • 2.9.22.AnnotationDefault
      • 2.9.23.BootstrapMethods
        • 2.9.23.1.bootstrap_methods_info
  • 3.总结
  • 9999.参考资料

前言

我们先来研究下JVM的输入文件,上文说到JVM的输入是字节码文件,但字节码是什么,长什么样,又是如何生成的呢?

这篇文章我们就来了解下这些信息。

本文我们先来看看 字节码文件长啥样,下一篇文章我再来分享 这个字节码是如何生成的。

1.下载&调试javaC源码

1.1.官网下载源码

大家可以从https://hg.openjdk.java.net/jdk8/jdk8/langtools 官网下载源码文件(左侧的.tar.gz或者.zip文件)
JVM系列(二)Class字节码探索_第1张图片

1.2.本地创建一个java工程

JVM系列(二)Class字节码探索_第2张图片

看我本地就是创建了一个javac的工程

然后将源码进行解压(注意:mac系统建议通过命令行方式进行解压)

将里面的javac的源码复制到这个工程中JVM系列(二)Class字节码探索_第3张图片

具体位置是:src/share/classes/com/sun/tools/javacJVM系列(二)Class字节码探索_第4张图片

这里不要复制其他的源码哈,不然容易出现很多【类找不到】的错误。别问我是咋知道的

1.3.执行

然后找到Main类,执行其中的main方法。会出现JVM系列(二)Class字节码探索_第5张图片

这种错误,代表没有源文件输入

然后我们在javac包同级创建一个examples这个包,做为我们学习的整个学习用例。我们先来创建一个helloWorld文件

package examples;

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/3/25 3:05 下午
 */
public class HelloWorld {
    public static void main(String[] args) {
        System.out.printf("hello world");
    }
}

然后在启动配置的Program arguments里加入 HelloWorld.java 的绝对路径。
JVM系列(二)Class字节码探索_第6张图片

再运行Main方法,会发现,在HelloWorld.java 同级别下生成了HelloWorld.class文件
JVM系列(二)Class字节码探索_第7张图片

1.4.断点运行

那我们咋看是如何生成的呢,启用万能的debug

我们在Main类的compile方法加个断点,但发现开始调试以后会发现不管怎么设置,调试都会进入tool.jar,没有走刚刚导入的源码。JVM系列(二)Class字节码探索_第8张图片

打开 Project Structure 页面(File->Project Structure), 选中图中 Dependencies 选项卡,把 `` 顺序调整到项目 JDK 的上面:调整好后是JVM系列(二)Class字节码探索_第9张图片

再重新断点,就可以进入源代码的断点啦。

1.5其他方式查看javac文件

我们还可以通过在IntelliJ IDEA上添加一个 tools来查看javac文件。

添加步骤

1.JVM系列(二)Class字节码探索_第10张图片

点击加号后填写以下信息JVM系列(二)Class字节码探索_第11张图片

  • Name&Description根据自己喜好填写
  • Program:填写的是本地jdk下的javap命令地址
  • Arguments:填写的是-c $FileClass$表示文件名称
  • Woring directory:填写的是$OutputPath$ 可直接将结果打印到控制台

然后就可以通过下面的这个入口来看到刚刚执行的java类的class文件

JVM系列(二)Class字节码探索_第12张图片

来个 效果图吧

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/3/23 10:58 上午
 */
public class FinallyTest {
    public void testFinally(){
        int a ;
        try {
            int b = 10;
        }finally {
            a = 3;
        }
    }

    public static void main(String[] args) {

    }
}

点击上面的tools ->java -c之后控制台输出的是JVM系列(二)Class字节码探索_第13张图片
这样一个内容

通过java -c我们能直观看到字节码长啥样,然后再通过源码,我们就能知道这个是如何生成的。

1.6.jclasslib

上面的java -c看到的是指令数据,能方便我们看到我们写的代码 具体转换成了哪些jvm指令(jvm指令也会有单独一篇文章介绍哈~)

而若我们想知道字节码的一些结构和属性,可以通过jclasslib来做到。jclasslib是一个IDEA的插件,所以可以通过插件方式找到并安装。具体步骤如下:

  1. 打开idea 中的settings > plugins 搜索 jclasslib 插件 进行安装 重启生效

  2. 重启后点击view > 选择show bytecode with jclasslibJVM系列(二)Class字节码探索_第14张图片

    点进去就是 可视化比较强的字节码信息了。JVM系列(二)Class字节码探索_第15张图片

我们后面的介绍会 通过jclasslib和真实的.class二进制文件 + 理论相结合的方式来介绍Class文件结构

2.class文件结构

想要了解Java语言书写的类是如何转变成字节码的就需要先知道Class文件的整体结构是啥样的,先知整体而后再看具体case案例。

2.1.class文件整体长啥样

我们先来看下官网给出的数据结构:JVM系列(二)Class字节码探索_第16张图片

其中 u1、u2、u4三种数据结构来表示1、2、4字节无符号整数。可以从图中得知:

class文件由下面十个部分组成:

  1. 魔数(Magic Number)
  2. 版本号(Minor&Major Version)
  3. 常量池(Constant Pool)
  4. 类访问标记(Access Flag)
  5. 类索引(This Class)
  6. 超类索引(Super Class)
  7. 接口表索引(Interface)
  8. 字段表(Field)
  9. 方法表(Method)
  10. 属性表(Attribute)

接下来,我们将通过这个例子来一一介绍上面的这些。

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/3/26 5:38 下午
 */
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

2.2.魔数

魔数是一个u4类型的数字,长4个字节;魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。也就是我们.class文件中固定的“cafe babe”JVM系列(二)Class字节码探索_第17张图片

若修改成其他的字符,则虚拟机将不识别;就好比.pdf 代表pdf文件,pdf阅读器能识别此文件,其他文件则不能识别一样。

2.3.版本号

在魔数之后的四个字节分别表示副版本号(Minor Version)和主版本号(Major Version)。JVM系列(二)Class字节码探索_第18张图片

从图中看到 副版本是0,主版本是0X0034也就是52;我们来看下jclasslib视图信息JVM系列(二)Class字节码探索_第19张图片

注意:

副版本号不同版本的Java虚拟机实现支持的版本号也不同,高版本号的Java虚拟机实现可以支持低版本号的Class文件,但低版本的Java虚拟机则不能运行高版本的Class文件。

2.4.常量池

常量池有两个字段 constant_pool_countconstant_pool

constant_pool_count 代表的是常量池的大小,用两个字节表示。假设常量池大小为n,常量池真正有效的索引是1~n-1。也就是说,如果constant_pool_count等于10,constant_pool数组的有效索引值是1~9。0属于保留索引,可供特殊情况使用。

我们来看看上面这个Hello World的常量池有多大;JVM系列(二)Class字节码探索_第20张图片

两个字节,所以是0022 ,这个是十六进制的数字,转变成十进制就是 34;也就是说 常量池大小是34;我们来通过jclasslib视图信息验证下:JVM系列(二)Class字节码探索_第21张图片

的确是34,然后看常量池中存储的是什么信息JVM系列(二)Class字节码探索_第22张图片

从图中能看到 常量池里包含各种字符串常量、类和接口名、字段名以及在类文件结构及其子结构中引用的其他常量。而且下标索引是从1~n-1的。

而常量池中的每个常量项cp_info是这样的:JVM系列(二)Class字节码探索_第23张图片

也就是包含两个信息:

  1. 一个字节的tag:代表的是常量项的类型
  2. 一个字节的info数组

2.4.1.tag常量项类型

Java虚拟机目前一共定义了14种常量项tag类型,这些常量名都以CONSTANT开头,以info结尾。具体如下表

类型 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

2.4.2.info信息

接下来,我们来看看常量池里的这些tag都存储了哪些,以及其结构式啥样的。

2.4.2.1.CONSTANT_Methodref_info

CONSTANT_Methodref_info类型数据,我们来看看这个的结构是啥样的。结构如下:

JVM系列(二)Class字节码探索_第24张图片

字段名称 字段类型 说明
tag U1 CONSTANT_Methodref_info 也就是10
class_index U2 常量池中的有效索引且常量池中这个索引对应的成员必须是CONSTANT_Class_info 结构,且必须是类
name_and_type_index U2 常量池的有效索引

常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。

在Fieldref_info中的这个 就必须是方法描述符 (

方法的参数最多有255个)
如果一个CONSTANT_Methodref_info结构的方法名以“<”(’\u003c’)开头,
则说明这个方法名是特殊的,
即这个方法是实例初始化方法,
它的返回类型必须为空。

我们来看看上面的HelloWorld的这个类型的数据:

JVM系列(二)Class字节码探索_第25张图片

  1. 这个是常量池中的第一个数据,index-1(注意这里不是从0开始的有,index=0是作为保留index的)
  2. 0X0a代表的就是tag=10
  3. 0006的两个字节就是class_index 也就是在常量池中index=6的数据的类型是CONSTANT_Class_info的数据。这个类型我们等下介绍。
  4. 0014的这两个字节就是name_and_type_index,也就是在常量池中index=20的数据是CONSTANT_NameAndType_info类型的数据,(这个类型我们等下介绍)

在这个二进制文件后面紧接着就是tag=9的数据,按照上面的表格,就是CONSTANT_Fieldref_info我们来看看这个类型的数据结构是啥样的

2.4.2.2.CONSTANT_Fieldref_info

JVM系列(二)Class字节码探索_第26张图片

字段名称 字段类型 说明
tag u1 CONSTANT_Fieldref 也就是9
class_index u2 常量池中的有效索引

且常量池中这个索引对应的成员必须是

CONSTANT_Class_info 结构,表示一个类或者接口
name_and_type_index u2 常量池的有效索引

常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。

在Fieldref_info中的这个 就必须是字段描述符

也就是说 二进制文件中的JVM系列(二)Class字节码探索_第27张图片
这些代表的是CONSTANT_Fieldref_info这个类型的数据,我们来看看存储了啥

  1. 这个数据是常量池的第二个数据,index=2
  2. 09是tag的值
  3. 0015的两个字节就是class_index 也就是在常量池中index=21的数据的类型是CONSTANT_Class_info的数据,代表类或者接口(index=21)
  4. 0016的两个字节就是name_and_type_index也就是在常量池中index=22的数据类型是CONSTANT_NameAndType_info的数据,代表字段或方法的描述符。

2.4.2.3.CONSTANT_String_info

JVM系列(二)Class字节码探索_第28张图片

字段名称 字段类型 说明
tag u1 CONSTANT_String 也就是8
string_index u2 是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,
表示一组Unicode码点序列,
这组Unicode码点序列最终会被初始化为一个String对象。

也就是说 二进制文件中的JVM系列(二)Class字节码探索_第29张图片

  1. 这个数据是常量池的第二个数据,index=3
  2. 08就是tag的值
  3. 0017这两个字节代表的就是string_index,也就是在常量池中index=33的CONSTANT_Utf8_info类型的String对象。而这个index=33的数据就是

2.4.2.4.其他数据

接着看上面的二进制文件,是0a开头的也就 代表着接下来的数据是CONSTANT_Methodref_info这个类型的数据,也就是JVM系列(二)Class字节码探索_第30张图片
这个是CONSTANT_Methodref_info这个类型的数据

  1. 这部分数据的index=4
  2. 0X0a代表的就是tag=10
  3. 0018的两个字节就是class_index 也就是在常量池中index=24的数据的类型是CONSTANT_Class_info的数据。这个类型我们等下介绍。
  4. 0019的这两个字节就是name_and_type_index,也就是在常量池中index=25的数据是CONSTANT_NameAndType_info类型的数据,(这个类型我们等下介绍)

================================================

继续看下一组,tag=07;也就是CONSTANT_Class_info类型

2.4.2.5.CONSTANT_Class_info

CONSTANT_Class_info的数据结构是:在这里插入图片描述

字段名称 字段类型 说明
tag u1 CONSTANT_Class的值,也就是7
name_index U2 对常量池的一个有效索引,指向CONSTANT_Utf8_info常量,这个字符串存储的是类或接口的全限定名

在二进制里就是JVM系列(二)Class字节码探索_第31张图片

  1. 属于常量池中第5个元素,index=5
  2. 07代表的是tag=7
  3. 001a这两个字节代表的是name_index,也就是在常量池中index=26的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。

================================================

继续看下一组,仍然是tag=07;也就是CONSTANT_Class_info类型

JVM系列(二)Class字节码探索_第32张图片

  1. 属于常量池中第6个元素,index=6
  2. 07代表的是tag=7
  3. 001b这两个字节代表的是name_index,也就是在常量池中index=27的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。

================================================

继续看下一组,仍然是tag=01;也就是CONSTANT_Utf8_info类型

2.4.2.6.CONSTANT_Utf8_info

数据结构如下:JVM系列(二)Class字节码探索_第33张图片

字段名称 字段类型 说明
tag u1 CONSTANT_Utf8也就是1
length u2 bytes[]数组的长度

CONSTANT_Utf8_info结构中的内容是以length属性确定长度而不是以null作为字符串的终结符
bytes[length] u1 bytes[]是表示字符串值的byte数组,bytes[]数组中每个成员的byte值都不会是0,也不在0xf0至0xff范围内

我们来看下二进制中是什么

JVM系列(二)Class字节码探索_第34张图片

  1. 属于常量池中第7个元素,index=7
  2. 01代表的是tag=1
  3. 0006的两个字节代表的是数组的长度,也就是6;从这块开始数6个字节为这个字符数组的值了,也就是图中标黄的地方。我们将上面的十六进制复制出来,然后用在线十六进制转字符串工具转换下JVM系列(二)Class字节码探索_第35张图片
    也就是字符串****

==========================

我们继续往下看

仍然是01开头的``类型数据JVM系列(二)Class字节码探索_第36张图片

  1. 属于常量池中第8个元素,index=8
  2. 01代表的是tag=1
  3. 0003的两个字节代表的是数组的长度,也就是3;从这块开始数3个字节为这个字符数组的值了,也就是图中标黄的地方。我们将上面的十六进制复制出来,然后用在线十六进制转字符串工具转换下JVM系列(二)Class字节码探索_第37张图片
    也就是字符串*()V*

以此类推,index【7-19】都是CONSTANT_Utf8_info类型的数据,数据分别是

index
7
8 ()V
9 Code
10 LineNumberTable
11 LocalVariableTable
12 this
13 Ljvm/classLib/HelloWorld;
14 main
15 ([Ljava/lang/String;)V
16 args
17 [Ljava/lang/String;
18 SourceFile
19 HelloWorld.java

也就是JVM系列(二)Class字节码探索_第38张图片

图片中标黄的地方 就是index[7-19]的值

继续往下看,tag=0c=12,也就是CONSTANT_NameAndType_info类型

2.4.2.7.CONSTANT_NameAndType_info

数据结构为:JVM系列(二)Class字节码探索_第39张图片

字段名称 字段类型 说明
tag u1 CONSTANT_NameAndType 也就是12
name_index u2 必须对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
descriptor_index u2 必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构表示一个有效的字段描述符或方法描述符。

我们来看二进制中内容:JVM系列(二)Class字节码探索_第40张图片

  1. 属于常量池中第20个元素,index=20
  2. 0c代表的是tag=12
  3. 0007的两个字节代表的是name_index,就是在常量池中index=7的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。(由上面信息可知index=7的对应字符串是)
  4. 0008的两个字节代表的是descriptor_index,就是在常量池中index=8的数据类型是CONSTANT_Utf8_info的字段或方法描述符(由上面信息可知index=8的对应字符串是**()V**)
  5. 综合起来看 这个代表的就是 类的无参且无返回值的init方法

==============================

咱们继续往下看,tag=07也就是CONSTANT_Class_info类型的数据

JVM系列(二)Class字节码探索_第41张图片

  1. 属于常量池中第21个元素,index=21
  2. 07代表的是tag=7
  3. 001c这两个字节代表的是name_index,也就是在常量池中index=28的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。(由下文可知index=28的字符串是java/lang/System)
  4. 综合看来这个描述的是System的这个类

==============================

咱们继续往下看,tag=0c也就是CONSTANT_NameAndType_info类型的数据

JVM系列(二)Class字节码探索_第42张图片

  1. 属于常量池中第22个元素,index=22
  2. 0c代表的是tag=12
  3. 001d的两个字节代表的是name_index,就是在常量池中index=29的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。(由上面信息可知index=29的对应字符串是out)
  4. 001e的两个字节代表的是descriptor_index,就是在常量池中index=30的数据类型是CONSTANT_Utf8_info的字段或方法描述符(由上面信息可知index=30的对应字符串是Ljava/io/PrintStream;
  5. 综合起来看 这个代表的就是

===============================================

我们继续往下看,tag=1,也就是CONSTANT_Utf8_info类型数据

JVM系列(二)Class字节码探索_第43张图片

  1. 属于常量池中第23个元素,index=23
  2. 01代表的是tag=1
  3. 000b的两个字节代表的是数组的长度,也就是12;从这块开始数12个字节为这个字符数组的值了,也就是图中标黄的地方。我们将上面的十六进制复制出来,然后用在线十六进制转字符串工具转换下JVM系列(二)Class字节码探索_第44张图片
    也就是字符串"hello world"

===================================

我们继续往下看,tag=7,也就是CONSTANT_Class_info类型的数据

JVM系列(二)Class字节码探索_第45张图片

  1. 属于常量池中第24个元素,index=24
  2. 07代表的是tag=7
  3. 001f这两个字节代表的是name_index,也就是在常量池中index=31的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。(由下面信息可知,index=31的字符串是java/io/PrintStream

===================================

我们继续往下看,tag=12,也就是CONSTANT_NameAndType_info类型的数据

JVM系列(二)Class字节码探索_第46张图片

  1. 属于常量池中第25个元素,index=25
  2. 0c代表的是tag=12
  3. 0020的两个字节代表的是name_index,就是在常量池中index=32的数据的类型是CONSTANT_Utf8_info的类或者接口的全限定名。(由上面信息可知index=32的对应字符串是println)
  4. 0021的两个字节代表的是descriptor_index,就是在常量池中index=33的数据类型是CONSTANT_Utf8_info的字段或方法描述符(由上面信息可知index=33的对应字符串是**(Ljava/lang/String;)V**)
  5. 综合起来看 这个代表的就是 带一个String类型参数的无返回值的pirntln方法

==============================================

我们继续往后看,发现,index【26-33】都是CONSTANT_Utf8_info类型的数据,JVM系列(二)Class字节码探索_第47张图片

数据分别是

index
26 jvm/classLib/HelloWorld
27 java/lang/Object
28 java/lang/System
29 out
30 Ljava/io/PrintStream;
31 java/io/PrintStream
32 println
33 (Ljava/lang/String;)V

==================

至此,这个HelloWorld类的常量池就到这,但常量池的类型还没有结束哈~

我们来继续看其他的类型

2.4.2.8.CONSTANT_Integer_info

数据结构:在这里插入图片描述

字段名称 字段类型 备注
tag u1 CONSTANT_Integer也就是3
bytes u4 表示int常量的值,按照Big-Endian的顺序存储。

2.4.2.9.CONSTANT_Float_info

数据结构:JVM系列(二)Class字节码探索_第48张图片

字段名称 字段类型 备注
tag u1 CONSTANT_Integer也就是4
bytes u4 按照IEEE 754单精度浮点格式.表示float常量的值,按照Big-Endian的顺序存储 bytes项的值首先被转换成一个int常量的bits:

如果bits值为0x7f800000,表示float值为正无穷。
如果bits值为0xff800000,表示float值为负无穷。
如果bits值在范围0x7f800001到0x7fffffff或者0xff800001到0xffffffff内,表示float值为NaN。
否则就按公式来计算

2.4.2.10.CONSTANT_Long_info

JVM系列(二)Class字节码探索_第49张图片

字段名称 字段类型 备注
tag u1 CONSTANT_Long 也就是5
high_bytes u4 无符号的high_bytes和low_bytes项用于共同表示long型常量,构造形式为((long) high_bytes << 32) + low_bytes,high_bytes和low_bytes都按照Big-Endian顺序存储。 所有的8字节的常量都占两个表成员(项)的空间。如果一个CONSTANT_Long_info或CONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。
low_bytes u4

2.4.2.11.CONSTANT_Double_info

JVM系列(二)Class字节码探索_第50张图片

字段名称 字段类型 备注
tag u1 CONSTANT_Double也就是6
high_bytes u4 high_bytes和low_bytes共同按照IEEE 754双精度浮点格式表示double常量的值。high_bytes和low_bytes都按照Big-Endian顺序存储 high_bytes项和low_bytes项的值首先被转换成一个int常量的bits:
如果bits值为0x7ff0000000000000L,表示double值为正无穷。
如果bits值为0xfff0000000000000L,表示double值为负无穷。
如果bits值在范围0x7ff0000000000001L到 0x7fffffffffffffffL或者0xfff0000000000001L到 0xffffffffffffffffL内,表示double值为NaN

否则就按公式来计算
low_bytes u4

2.4.2.12.CONSTANT_MethodHandle_info

JVM系列(二)Class字节码探索_第51张图片

字段名称 字段类型 备注
tag u1 CONSTANT_MethodHandle也就是15
reference_kind u1 必须在1至9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为 具体类型见下面单独的子表

如果reference_kind项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段创建的方法句柄。
如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那么常量池在reference_index索引处的项必须是CONSTANT_Methodref_info结构,表示由类的方法或构造函数创建的方法句柄。
如果reference_kind项的值是9(REF_invokeInterface),那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法创建的方法句柄。
如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。
如果reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化()方法
reference_index U2 必须是对常量池的有效索引

2.4.2.13.CONSTANT_MethodType_info

JVM系列(二)Class字节码探索_第52张图片

字段名称 字段类型 备注
tag u1 CONSTANT_MethodType 也就是16
descriptor_index u2 必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符

2.4.2.14.CONSTANT_InvokeDynamic_info

JVM系列(二)Class字节码探索_第53张图片

字段名称 字段类型 说明
tag u1 CONSTANT_InvokeDynamic 也就是18
bootstrap_method_attr_index u2 必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
name_and_type_index u2 常量池的有效索引

2.4.2.15.reference_kind类型

对应英文名称 含义 说明
1 REF_getField
2 REF_getStatic
3 REF_putField
4 REF_putStatic
5 REF_invokeVirtual
6 REF_invokeStatic
7 REF_invokeSpecial
8 REF_newInvokeSpecial
9 REF_invokeInterface

2.5.类访问标记

紧随常量池之后的区域是访问标记(Access flags),用来标识一个类为final、abstract等,由两个字节表示,总共有16个标记位可供使用,目前只使用了其中的8个

2.5.1.类access_flags类型表

类型名称 十六进制值 定义 解释说明
ACC_PUBLIC 0x0001 声明为公共的;可以从其包外部访问
ACC_FINAL 0x0010 声明为final;不允许有子类
ACC_SUPER 0x0020 当由invokespecial指令调用时,需要特别处理的超类方法。
ACC_INTERFACE 0x0200 标识是一个接口而非类 有这个标识的类表示其是接口,而非类,则会同时再被标记为ACC_ABSTRACT不拥有ACC_FINAL和ACC_ENUM 的标记
·ACC_ABSTRACT 0x0400 声明为抽象的;不能实例化。
ACC_SYNTHETIC 0x1000 声明为合成的;不存在于源代码中。表明这个类是由编译器自己产生的而不是我们编写的代码生成的
ACC_ANNOTATION 0x2000 标识是一个注解
ACC_ENUM 0x4000 标识是一个枚举类型;可以是这个类或者其父类都是枚举类型

所以我们来继续看上面的HelloWorld的二进制文件JVM系列(二)Class字节码探索_第54张图片

0021也就是ACC_SUPERACC_PUBLIC

但这些访问标记并不是可以随意组合的,比如ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED不能同时设置,ACC_FINAL和ACC_ABSTRACT也不能同时设置,否则会违背语义。更多的规则可以在javac源码的com.sun.tools.javac.comp.Check.java文件中找到。

2.5.2.内部类access_flags类型表

类型名称 十六进制值 含义 解释说明
ACC_PUBLIC 0x0001 源文件定义public
ACC_PRIVATE 0x0002 源文件定义private
ACC_PROTECTED 0x0004 源文件定义protected
ACC_STATIC 0x0008 源文件定义static
ACC_FINAL 0x0010 源文件定义final
ACC_INTERFACE 0x0200 源文件定义interface
ACC_ABSTRACT 0x0400 源文件定义abstract
ACC_SYNTHETIC 0x1000 声明synthetic,非源文件定义
ACC_ANNOTATION 0x2000 声明annotation
ACC_ENUM 0x4000 声明enum

2.6.超类索引&接口表索引

this_class、super_name、interfaces

这三部分用来确定类的继承关系,this_class表示类索引,super_name表示直接父类的索引,interfaces表示类或者接口的直接父接口。this_class是一个指向常量池的索引,表示类或者接口的名字,用两字节表示,

2.7.字段表

紧随接口索引表之后的是字段表(fields),类中定义的字段会被存储到这个集合中,包括静态和非静态的字段,它的结构如下:JVM系列(二)Class字节码探索_第55张图片

字段表也是一个变长的结构,fields_count表示field的数量,接下来的fields表示字段集合,共有fields_count个,每一个字段用field_info结构表示,

字段名称 类型 含义说明 备注
access_flags u2 用于定义字段被访问权限和基础属性的掩码标志,,具体见Field的access_flag表
name_index u2 对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的非全限定名。
descriptor_index u2 必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效字段的描述符
attributes_count u2 表示当前字段的附加属性的数量
attributes[attributes_count] attribute_info attributes表的每一个成员的值必须是attribute结构,一个字段可以有任意个关联属性

2.7.1.字段access_flags类型表

类型名称 十六进制值 定义 解释说明
ACC_PUBLIC 0x0001 public,表示字段可以从任何包访问
ACC_PRIVATE 0x0002 private,表示字段仅能该类自身调用
ACC_PROTECTED 0x0004 protected,表示字段可以被子类调用
ACC_STATIC 0x0008 static,表示静态字段
ACC_FINAL 0x0010 final,表示字段定义后值无法修改
ACC_VOLATILE 0x0040 volatile,表示字段是易变的
ACC_TRANSIENT 0x0080 transient,表示字段不会被序列化
ACC_SYNTHETIC 0x1000 表示字段由编译器自动产生 表明这个字段是由编译器自己产生的而不是我们编写的代码生成的
ACC_ENUM 0x4000 enum,表示字段为枚举类型

2.7.2.字段描述符

字段描述符(field descriptor)用来表示某个field的类型,在JVM中定义一个int类型的字段时,类文件中存储的类型并不是字符串int,而是更精简的字母I。根据类型的不同,字段描述符分为三大类。

  1. 原始类型,byte、int、char、float等这些简单类型使用一个字符来表示,比如J对应long类型,B对应byte类型。
  2. 引用类型使用L;的方式来表示,为了防止多个连续的引用类型描述符出现混淆,引用类型描述符最后都加了一个“;”作为结束,比如字符串类型String的描述符为“Ljava/lang/String;”。
  3. JVM使用一个前置的“[”来表示数组类型,如int[]类型的描述符为“[I”,字符串数组String[]的描述符为“[Ljava/lang/String;”。而多维数组描述符只是多加了几个“[”而已,比如Object[][][]类型的描述符为“[[[Ljava/lang/Object;”。

完整的字段类型描述符:

描述符 代表含义 解释说明
B byte signed byte
C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L ClassName ; reference an instance of class ClassName
S short signed short
Z boolean true or false
[ reference one array dimension

JVM系列(二)Class字节码探索_第56张图片

2.7.3.字段属性

与字段相关的属性包括ConstantValue、Synthetic、Signature、Deprecated、Runtime-Visible-Annotations和RuntimeInvisibleAnnotations这6个,比较常见的是ConstantValue属性,用来表示一个常量字段的值。具体见2.9

2.8.方法表

方法表的数据结构:JVM系列(二)Class字节码探索_第57张图片

具体说明:

字段名称 类型 说明
access_flags u2 用于定义当前方法的访问权限和基本属性的掩码标志
name_index u2 必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,它要么表示初始化方法的名字(或),要么表示一个方法的有效的非全限定名
descriptor_index u2 必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的方法的描述符
attributes_count u2 方法属性个数
attributes[attributes_count] attribute_info attributes表的每一个成员的值必须是attribute结构,一个方法可以有任意个关联属性

2.8.1方法access_flags类型表

类型名称 十六进制值 含义 解释说明
ACC_PUBLIC 0x0001 public,方法可以从包外访问
ACC_PRIVATE 0x0002 private,方法仅能该类自身调用
ACC_PROTECTED 0x0004 protected,方法可以被子类调用
ACC_STATIC 0x0008 static,表示静态方法
ACC_FINAL 0x0010 final,方法不能被重写(覆盖)
ACC_SYNCHRONIZED 0x0020 synchronized,方法由管程同步
ACC_BRIDGE 0x0040 bridge,方法由编译器产生
ACC_VARARGS 0x0080 表示方法带有变长参数 说明方法在源码层的参数列表是否变长的。如果是变长的,则在编译时,方法的ACC_VARARGS标志设置1,其余的方法ACC_VARARGS标志设置为0
ACC_NATIVE 0x0100 native,方法引用非java语言的本地方法
ACC_ABSTRACT 0x0400 abstract,方法没有具体实现
ACC_STRICTFP 0x0800 strictfp,方法使用FP-strict浮点格式
ACC_SYNTHETIC 0x1000 方法在源文件中不出现,由编译器产生 表明这个方法是由编译器自己产生的而不是我们编写的代码生成的

2.8.2.方法名&方法描述符

2.8.3.方法属性

方法属性表是method_info结构的最后一部分。前面介绍了方法的访问标记和方法签名,还有一些重要的信息没有出现,如方法声明抛出的异常,方法的字节码,方法是否被标记为deprecated等,属性表就是用来存储这些信息的。与方法相关的属性有很多,其中比较重要的是Code和Exceptions属性,其中Code属性存放方法体的字节码指令,Exceptions属性用于存储方法声明抛出的异常。

我们来看下上面的HelloWorld的二进制文件,来分析下:JVM系列(二)Class字节码探索_第58张图片

从红色方框开始表示的就是方法啦0002这两个字节代表的就是这个类有2个方法,然后 黄色方框标记的0001~绿色方框0009之前描述的都是一个方法,我们来仔细瞅瞅.

第一个方法:

  1. 0001表示的是方法的access_flag类型。这个表示的是public类型
  2. 0007表示的是name_index,也就是说 在常量池index=7的位置放的是这个方法的名称,由上面信息可知,这个方法名是"init"
  3. 0008表示的是descriptor_index方法描述符的index,也就是说在常量池index=8的位置放的是这个方法的描述符,由上面信息可知道,这个方法的描述符是“()V”
  4. 0001表示的是方法的attributes_count ,代表这个方法有1个属性,接下来的二进制就是讲解 方法属性的了
  5. 0009表示的是attribute_name_index也就是说在常量池indeex=9的位置方式的是这个方法属性的名称,由上面信息可知,这个是“Code”(这个的含义后面讲解)
  6. 0000002f表示的是属性的长度,也就是说这个方法有47个属性,也就是这个后面的47个字节用来代表这个方法的属性
  7. JVM系列(二)Class字节码探索_第59张图片
    红框中的这些就是代表 属性的二进制;这个里面的具体内容等2.9节给大家讲解

再来看第二个方法

JVM系列(二)Class字节码探索_第60张图片

  1. 0009表示的是方法的access_flag类型。这个表示的是static、public类型
  2. 000e表示的是name_index,也就是说 在常量池index=14的位置放的是这个方法的名称,由上面信息可知,这个方法名是"main"
  3. 000f表示的是descriptor_index方法描述符的index,也就是说在常量池index=15的位置放的是这个方法的描述符,由上面信息可知道,这个方法的描述符是“([Ljava/lang/String;)V”
  4. 0001表示的是方法的attributes_count ,代表这个方法有1个属性,接下来的二进制就是讲解 方法属性的了
  5. 0009表示的是attribute_name_index也就是说在常量池indeex=9的位置方式的是这个方法属性的名称,由上面信息可知,这个是“Code”(这个的含义后面讲解)
  6. 00000037表示的是属性的长度,也就是说这个方法有56个属性,也就是这个后面的47个字节用来代表这个方法的属性,也就是上面标蓝色的那部分数据

2.9.属性表

在方法表之后的结构是class文件的最后一部分——属性表。属性出现的地方比较广泛,不只出现在字段和方法中,在顶层的class文件中也会出现。相比于常量池只有14种固定的类型,属性表的类型更加灵活,不同的虚拟机实现厂商可以自定义属性,属性表的结构:

在这里插入图片描述

字段名称 类型 说明
attribute_name_index u2 必须是对当前Class文件的常量池的有效16位无符号索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示当前属性的名字
attribute_length u4 给出了跟随其后的字节的长度,这个长度不包括attribute_name_index和attribute_name_index项的6个字节。
info[attribute_length] u1

我们来看下我们一直用的HelloWorld的最后一部分数据:JVM系列(二)Class字节码探索_第61张图片

  1. 0001代表属性的个数,也就是这个类只有1个属性
  2. 0012代表的是attribute_name_index,也就是在常量池中index=18的地方存储的是属性的名称,(由上面信息可知,这个是"SourceFile")
  3. 00000002这4个字节代表的是属性对应的字节长度,也就是说这个属性长度为2个字节
  4. 0013的这两个字节就是属性的内容在常量池中对应的index;也就是在常量池中index=19的地方存储的是“SourceFile”对应的源文件的名字(由上面信息可知,index=19对应的就是“HelloWorld.java”)

到这,简单的HelloWorld的class文件我们就全部分析完了。

虚拟机预定义了20多种属性。分别是:

2.9.1预定义属性

属性名称 首次出现的SE版本 首次出现的Class文件版本
属性名称 首次出现的SE版本 首次出现的Class文件版本
ConstantValue 1.0.2 45.3
Code 1.0.2 45.3
StackMapTable 6 50.0
Exceptions 1.0.2 45.3
InnerClasses 1.1 45.3
EnclosingMethod 5.0 49.0
Synthetic 1.1 45.3
Signature 5.0 49.0
SourceFile 1.0.2 45.3
SourceDebugExtension 5.0 49.0
LineNumberTable 1.0.2 45.3
LocalVariableTable 1.0.2 45.3
LocalVariableTypeTable 5.0 49.0
Deprecated 1.1 45.3
RuntimeVisibleAnnotations 5.0 49.0
RuntimeInvisibleAnnotations 5.0 49.0
RuntimeVisibleParameterAnnotations 5.0 49.0
RuntimeInvisibleParameterAnnotations 5.0 49.0
AnnotationDefault 5.0 49.0
BootstrapMethods 7 51.0

2.9.2.ConstantValue

  1. 定长属性
  2. 位于field_info结构的属性表中。ConstantValue属性表示一个常量字段的值
  3. 在一个field_info结构的属性表中最多只能有一个ConstantValue属性。也就是属性若是常量只可能代表以一个常量值
  4. 如果该字段为静态类型(即field_info结构的access_flags项设置了ACC_STATIC标志),则说明这个field_info结构表示的常量字段值将被分配为它的ConstantValue属性表示的值,这个过程也是类或接口申明的常量字段(Constant Field)初始化的一部分。这个过程发生在引用类或接口的类初始化方法执行之前。
  5. 如果field_info结构表示的非静态字段包含了ConstantValue属性,那么这个属性必须被虚拟机所忽略。

数据结构为:JVM系列(二)Class字节码探索_第62张图片

字段名称 字段类型 字段说明
attribute_name_index u2 必须是一个对常量池的有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“ConstantValue”。
attribute_length u4 固定为2
constantvalue_index u2 必须是一个对常量池的有效索引。常量池在该索引处的项给出该属性表示的常量值。

2.9.2.1.constantvalue_index

Table 4.7. Constant value attribute types

Field Type Entry Type
long CONSTANT_Long
float CONSTANT_Float
double CONSTANT_Double
int, short, char, byte, boolean CONSTANT_Integer
String CONSTANT_String

我们来通过一个例子看看

package jvm.classLib.attributeTest;

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/4/2 3:08 下午
 */
public class ConstantValueTest {
    private static int age = 18;

    public static void main(String[] args) {
        System.out.println(age);
    }
}

我们来通过jclasslib看看是咋存储的,JVM系列(二)Class字节码探索_第63张图片

  1. 在字段中有index=8的常量池的名称索引和描述符索引
  2. 访问标记也是private static

2.9.3.Code

  1. 变长属性
  2. 位于method_info结构的属性表
  3. 一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。
  4. 如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它情况下,method_info有必须有明确的Code属性。
字段名称 字段类型 字段说明
attribute_name_index u2 必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“Code”。
attribute_length u4 表示当前属性的长度,不包括开始的6个字节
max_stack u2 给出了当前方法的操作数栈在运行执行的任何时间点的最大深度 这个字段存储为2个字节,所以方法栈帧中的操作数栈的大小深度为65535 此外:每个long和double类型的数据被认为占用max_locals中的两个单元,所以使用这些类型时,操作数栈的限制的最大值就会相应地减少
max_locals u2 给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。long和double型的局部变量的最大索引是max_locals-2,其它类型的局部变量的最大索引是max_locals-1. 这个字段存储为2个字节,所以方法调用时创建的栈帧的局部变量表中的最大局部变量个数65535个
code_length u4 给出了当前方法的code[]数组的字节数,code_length的值必须大于0,即code[]数组不能为空
code[code_length] u1 给出了实现当前方法的Java虚拟机字节码。code[]数组以按字节寻址的方式读入机器内存,如果code[]数组的第一个字节是按以4字节边界对齐的话,那么tableswitch和lookupswitch指令中所有涉及到的32位偏移量也都是按4字节长度对齐的
exception_table_length u2 给出了exception_table[]数组的成员个数量
exception_table[exception_table_length] exception_info 异常处理器,值的顺序有含义,不能随意修改
attributes_count u2 给出了Code属性中attributes表的成员个数
attributes[attributes_count] attribute_info 成员只能是LineNumberTable,LocalVariableTable,LocalVariableTypeTable和StackMapTable属性 这三者

我们来看上面的HelloWorld的第二个main方法的code信息

JVM系列(二)Class字节码探索_第64张图片

从黄色框的0009开始的蓝色框表示的就是Code的内容,我们来具体分析下:

  1. 0009的这两个字节就是index=9在常量池中存储的就是"Code"
  2. 0000 0037这四个字节存储的就是当前属性的长度,也就是后面的55个字节都是这个Code的内容
  3. 0002的两个字节代表的是max_stack,也就是操作数栈的最大深度是2,方法执行的任意期间操作数栈的深度都不会超过这个值。它的计算规则是:有入栈的指令stack增加,有出栈的指令stack减少,在整个过程中stack的最大值就是max_stack的值,增加和减少的值一般都是1,但也有例外:LONG和DOUBLE相关的指令入栈stack会增加2,VOID相关的指令则为0。
  4. 0001的两个字节代表的是max_locals,是局部变量表的大小,它的值并不等于方法中所有局部变量的数量之和。当一个局部作用域结束,它内部的局部变量占用的位置就可以被接下来的局部变量复用了。
  5. 0000 0009的四个字节代表的是code_length,表示字节码指令的长度;code是一个长度为code_length的字节数组,存储真正的字节码指令;也就是说接下来的9个字节表示的是真正的字节码指令
  6. b200 0212 03b6 0004 b1表示的就是真正的字节码指令
  7. 00 00的两个字节代表的是exception_table_length,异常表长度,为0
  8. 00 02的两个字节代表的就是 Code的属性个数,也就是Code有两个属性:
    1. 属性1:
      1. 000a表示的是第一个属性的attribute_name_index,也就是index=10在常量池中存储的是这个属性的名称,也就是“LineNumberTable”
      2. 00 00 00 0a四个字节表示的是属性的长度,也就是"LineNumberTable"这个属性长10个字节,
      3. 00 0200 0000 0a00 0800 0b是代表“LineNumberTable”属性的内容.
    2. 属性2:
      1. 000b 代表的是第一个属性的attribute_name_index,也就是index=11在常量池中存储的是这个属性的名称,也就是“LocalVariableTable”
      2. 0000 000c代表的是属性的长度,也就是说属性长度是12.
      3. 00 0100 0000 0900 1000 1100 00就是“LocalVariableTable”属性的内容。

2.9.4.exception_info

字段名称 字段类型 字段说明
start_pc u2 start_pc和end_pc两项的值表明了异常处理器在code[]数组中的有效范围。start_pc必须是对当前code[]数组中某一指令的操作码的有效索引,end_pc要么是对当前code[]数组中某一指令的操作码的有效索引,要么等于code_length的值,即当前code[]数组的长度。start_pc的值必须比end_pc小。当程序计数器在范围[start_pc, end_pc)内时,异常处理器就将生效。即设x为异常句柄的有效范围内的值,x满足:start_pc ≤ x < end_pc。
end_pc u2
handler_pc u2 表示一个异常处理器的起点,它的值必须同时是一个对当前code[]数组中某一指令的操作码的有效索引。
catch_type u2 如果catch_type项的值不为0,那么它必须是对常量池的一个有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示当前异常处理器指定需要捕捉的异常类型。只有当抛出的异常是指定的类或其子类的实例时,异常处理器才会被调用。如果catch_type项的值如果为0,那么这个异常处理器将会在所有异常抛出时都被调用。这可以用于实现finally语句

2.9.5.StackMapTable

  1. 变长属性
  2. 位于Code属性的属性表中。这个属性会在虚拟机类加载的类型阶段被使用
  3. 包含0至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型(Verification Types )
  4. 类型检测器(Type Checker)会检查和处理目标方法的局部变量和操作数栈所需要的类型。
字段名称 字段类型 字段说明
attribute_name_index u2 必须是对常量池的有效索引,常量池在该索引的项处必须是CONSTANT_Utf8_info结构,表示“StackMapTable”字符串。
attribute_length u4 表示当前属性的长度,不包括开始的6个字节
number_of_entries u2 给出了entries表中的成员数量。Entries表的每个成员是都是一个stack_map_frame结构的项
entries[number_of_entries] stack_map_frame 给出了当前方法所需的stack_map_frame结构

2.9.6.Exceptions

  1. 是一个变长属性
  2. 它位于method_info结构的属性表中。
  3. Exceptions属性指出了一个方法需要检查的可能抛出的异常
字段名称 字段类型 字段说明
attribute_name_index u2 必须是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串"Exceptions"
attribute_length u4 给出了当前属性的长度,不包括开始的6个字节。
number_of_exceptions u2 exception_index_table[]数组中成员的数量。
exception_index_table[number_of_exceptions] u2 每个成员的值都必须是对常量池的有效索引。常量池在这些索引处的成员必须都是CONSTANT_Class_info结构,表示这个方法声明要抛出的异常的类的类型。 一个方法如果要抛出异常,必须至少满足下列三个条件中的一个: 要抛出的是RuntimeException或其子类的实例。 要抛出的是Error或其子类的实例。 要抛出的是在exception_index_table[]数组中申明的异常类或其子类的实例。 这些要求没有在Java虚拟机中进行强制检查,它们只在编译时进行强制检查

我们来看个例子

package jvm.classLib.attributeTest;

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/4/2 4:23 下午
 */
public class ExceptionTest {
    private void test() throws Exception {
        throw new Exception("test throw exception");
    }

    public static void main(String[] args) {

    }
}

JVM系列(二)Class字节码探索_第65张图片

看到在方法里有Exceptions 表,表内有详细信息。

2.9.7.InnerClasses

  1. 是一个变长属性
  2. 位于ClassFile结构的属性表
  3. 为内部类专用的
字段名称 字段类型 字段说明
attribute_name_index u2 必须是一个对常量池的有效引用。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串"InnerClasses"
attribute_length u4 给出了这个属性的长度,不包括前6个字节
number_of_classes u2 表示classes[]数组的成员数量。
classes[number_of_classes] inner_classes_info 每个成员为inner_classes_info类型常量池中的每个CONSTANT_Class_info结构如果表示的类或接口并非某个包的成员,则每个类或接口在classes[]数组中都有一个成员与之对应。 如果Class中包含某些类或者接口,那么它的常量池(以及对应的InnerClasses属性)必须包含这些成员,即使某些类或者接口没有被这个Class使用过。 这条规则暗示着内部类或内部接口成员在其被定义的外部类(Enclosing Class)中都会包含有它们的InnerClasses信息。

2.9.7.1.inner_classes_info

字段名称 字段类型 字段说明
inner_class_info_index u2 内部类信息在常量池中的索引
outer_class_info_index u2 外部类信息在常量池中的索引
inner_name_index u2 内部类名称在常量池中的索引,匿名类则这个值为0
inner_class_access_flags u2 表示内部类的访问权限及基础属性

2.9.8.EnclosingMethod

  1. 可选的定长属性
  2. 位于ClassFile结构的属性表。
  3. 当且仅当Class为局部类或者匿名类时,才能具有EnclosingMethod属性。
  4. 一个类最多只能有一个EnclosingMethod属性。
字段名称 字段类型 字段说明
attribute_name_index u2 必须是一个对常量池的有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串"EnclosingMethod"。
attribute_length u4 值固定为4
class_index u2 必须是一个对常量池的有效索引。常量池在该索引出的项必须是CONSTANT_Class_info结构,表示包含当前类声明的最内层类。
method_index u2 如果当前类不是在某个方法或初始化方法中直接包含(Enclosed),那么method_index值为0,否则method_index项的值必须是对常量池的一个有效索引,常量池在该索引处的成员必须是CONSTANT_NameAndType_info结构,表示由class_index属性引用的类的对应方法的方法名和方法类型。Java编译器有责任在语法上保证通过method_index确定的方法是语法上最接近那个包含EnclosingMethod属性的类的方法(Closest Lexically Enclosing Method)。

2.9.9.Synthetic

  1. 定长属性
  2. 位于ClassFile中的属性表。
  3. 如果一个类成员没有在源文件中出现,则必须标记带有Synthetic属性,或者设置ACC_SYNTHETIC标志。
  4. 唯一的例外是某些与人工实现无关的、由编译器自动产生的方法,也就是说,Java编程语言的默认的实例初始化方法(无参数的实例初始化方法)、类初始化方法,以及Enum.values()和Enum.valueOf()等方法是不用使用Synthetic属性或ACC_SYNTHETIC标记的。
  5. Synthetic属性是在JDK 1.1中为了支持内部类或接口而引入的。
字段名称 字段类型 字段说明
attribute_name_index u2 是对常量池的一个有效索引,常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“Synthetic”。
attribute_length u4 固定为0

2.9.10.Signature

  1. 可选的定长属性
  2. 位于ClassFile,field_info或method_info结构的属性表中。
  3. 在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。
字段名称 字段类型 字段说明
attribute_name_index u2 必须是一个对常量池的有效索引。常量池在索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“Signature”。
attribute_length u4 固定为2
signature_index u2 必须是一个对常量池的有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示类签名或方法类型签名或字段类型签名:如果当前的Signature属性是ClassFile结构的属性,则这个结构表示类签名,如果当前的Signature属性是method_info结构的属性,则这个结构表示方法类型签名,如果当前Signature属性是field_info结构的属性,则这个结构表示字段类型签名。

这个其实对应到我们的代码中就是这类的范型,我们来举例子看看

例子一:范型在属性上

package jvm.classLib;

/**
 * @Description
 * @Author 大龄波妞
 * @Date 2021/3/23 9:47 下午
 */
public class SignatureClassTest<T> {
    private T methodName;
    public void print(T value){

    }

    public SignatureClassTest(T  methodName) {
    }

    public static void main(String[] args) {
        System.out.println(
                SignatureClassTest.class.getName());
    }
}

JVM系列(二)Class字节码探索_第66张图片

可以看到在字段里有Signature,然后里面有属性信息和签名信息。我们再来看范型在类上的JVM系列(二)Class字节码探索_第67张图片

这块存储了范型类型,且是继承Object类型的;这块有个“范型擦除”的黑科技,我们后文再说

2.9.11.SourceFile

  1. 可选定长字段
  2. 位于ClassFile结构的属性表。
  3. 一个ClassFile结构中的属性表最多只能包含一个SourceFile属性。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“SourceFile”。
attribute_length u4 固定为2
sourcefile_index u2 必须是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个字符串。sourcefile_index项引用字符串表示被编译的Class文件的源文件的名字。不包括源文件所在目录的目录名,也不包括源文件的绝对路径名。平台相关(绝对路径名等)的附加信息必须是运行时解释器(Runtime Interpreter)或开发工具在文件名实际使用时提供。

2.9.12.SourceDebugExtension

  1. 可选属性
  2. 位于ClassFile结构属性表。
  3. ClassFile结构的属性表最多只能包含一个SourceDebugExtension属性
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“SourceDebugExtension”。
attribute_length u4 给出了当前属性的长度,不包括开始的6个字节。即attribute_length项的值是字节数组debug_extension[]数组的长度
debug_extension[attribute_length] u1 用于保存扩展调试信息,扩展调试信息对于Java虚拟机来说没有实际的语义。这个信息用改进版的UTF-8编码的字符串表示,这个字符串不包含byte值为0的终止符。需要注意的是,debug_extension[]数组表示的字符串可以比Class实例对应的字符串更长。

2.9.13.LineNumberTable

  1. 是可选变长属性
  2. 位于Code结构的属性表。
  3. 它被调试器用于确定源文件中行号表示的内容在Java虚拟机的code[]数组中对应的部分。
  4. 在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现,
  5. 此外,多个LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即LineNumberTable属性不需要与源文件的行一一对应。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“LineNumberTable”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
line_number_table_length u2 line_number_table[]数组的成员个数。
line_number_table[line_number_table_length] line_number_info 每个成员都表明源文件中行号的变化在code[]数组中都会有对应的标记点

2.9.13.1.line_number_info

JVM系列(二)Class字节码探索_第68张图片

字段名称 字段类型 字段说明
start_pc u2 start_pc项的值必须是code[]数组的一个索引,code[]数组在该索引处的字符表示源文件中新的行的起点。start_pc项的值必须小于当前LineNumberTable属性所在的Code属性的code_length项的值。
line_number u2 line_number项的值必须与源文件的行数相匹配

2.9.14.LocalVariableTable

  1. 是可选变长属性
  2. 位于Code属性的属性表中。
  3. 它被调试器用于确定方法在执行过程中局部变量的信息。
  4. 在Code属性的属性表中,LocalVariableTable属性可以按照任意顺序出现。
  5. Code属性中的每个局部变量最多只能有一个LocalVariableTable属性。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“LocalVariableTable”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
local_variable_table_length u2 local_variable_table[]数组的成员个数。
local_variable_table[local_variable_table_length] local_variable_info 每一个成员表示一个局部变量的值在code[]数组中的偏移量范围。它同时也是用于从当前帧的局部变量表找出所需的局部变量的索引。

2.9.14.1.local_variable_info

字段名称 字段类型 字段说明
start_pc u2 所有给定的局部变量的索引都在范围[start_pc, start_pc+length)中,即从start_pc(包括自身值)至start_pc+length(不包括自身值)。start_pc的值必须是一个对当前Code属性的code[]数组的有效索引,code[]数组在这个索引处必须是一条指令操作码。start_pc+length要么是当前Code属性的code[]数组的有效索引,code[]数组在该索引处必须是一条指令的操作码,要么是刚超过code[]数组长度的最小索引值。
length u2
name_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个局部变量的有效的非全限定名
descriptor_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个局部变量类型的字段描述符
index u2 index为此局部变量在当前栈帧的局部变量表中的索引。如果在index索引处的局部变量是long或double型,则占用index和index+1两个索引。

2.9.15.LocalVariableTypeTable

  1. 是可选变长属性
  2. 位于Code的属性表。
  3. 它被用于给调试器确定方法在执行中局部变量的信息,在Code属性的属性表中,LocalVariableTable属性可以按照任意顺序出现。
  4. Code属性中的每个局部变量最多只能有一个LocalVariableTable属性。
  5. LocalVariableTypeTable属性和LocalVariableTable属性并不相同,LocalVariableTypeTable提供签名信息而不是描述符信息。
  6. 这仅仅对泛型类型有意义。泛型类型的属性会同时出现在LocalVariableTable属性和LocalVariableTypeTable属性中,其他的属性仅出现在LocalVariableTable属性表中。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“LocalVariableTypeTable”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
local_variable_type_table_length u2 local_variable_type_table[]数组的成员个数。
local_variable_type_table[local_variable_type_table_length] local_variable_type_info 每一个成员表示一个局部变量的值在code[]数组中的偏移量范围。它同时也是用于从当前帧的局部变量表找出所需的局部变量的索引。

2.9.15.1.local_variable_type_info

字段名称 字段类型 字段说明
start_pc u2 所有给定的局部变量的索引都在范围[start_pc, start_pc+length)中,即从start_pc(包括自身)至start_pc+length(不包括自身)。start_pc的值必须是一个对当前Code属性的code[]数组的有效索引,code[]数组在这个索引处必须是一条指令操作码。start_pc+length要么是当前Code属性的code[]数组的有效索引,code[]数组在该索引处必须是一条指令的操作码,要么是刚超过code[]数组长度的最小索引值。
length u2
name_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构表示一个局部变量的有效的非全限定名
signature_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构表示一个局部变量类型的字段签名
index u2 index为此局部变量在当前栈帧的局部变量表中的索引。如果在index索引处的局部变量是long或double型,则占用index和index+1两个索引。

2.9.16.Deprecated

  1. 可选定长属性
  2. 位于ClassFile, field_info 或 method_info结构的属性表中。
  3. 类、接口、方法或字段都可以带有为Deprecated属性,如果类、接口、方法或字段标记了此属性,则说明它将会在后续某个版本中被取代。
  4. 在运行时解释器或工具(譬如编译器)读取Class文件格式时,可以用Deprecated属性来告诉使用者避免使用这些类、接口、方法或字段,选择其他更好的方式。
  5. Deprecated属性的出现不会修改类或接口的语义。
  6. Deprecated属性是在JDK 1.1为了支持注释中的关键词@deprecated而引入的。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“Deprecated”。
attribute_length u4 固定为0

2.9.17.RuntimeVisibleAnnotations

  1. 是可变长属性
  2. 位于ClassFile, field_info或method_info结构的属性表中。
  3. RuntimeVisibleAnnotations用于保存Java语言中的类、字段或方法的运行时的可见注解(Annotations)。
  4. 每个ClassFile,field_info和method_info结构最多只能含有一个RuntimeVisibleAnnotations属性为当前的程序元素保存所有的运行时可见的Java语言注解。Java虚拟机必须支持这些注解可被反射的API使用它们。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“RuntimeVisibleAnnotations”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
num_annotations u2 annotations[]数组的成员个数。
annotations[num_annotations] annotation 每个成员的值表示一个程序元素的唯一的运行时可见注解

2.9.18.annotation

字段名称 字段类型 字段说明
type_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构表示一个字段描述符,这个字段描述符表示一个注解类型,和当前annotation结构表示的注解一致
num_element_value_pairs u2 给出了当前annotation结构表示的注解的键值对(键值对的格式为:元素-值)的数量,即element_value_pairs[]数组成员数量。需要注意的是,在单独一个注解中可能含有数量最多为65535个键值对。
element_value_pairs[num_element_value_pairs] element_value_pairs_info 每一个成员的值对应当前annotation结构表示的注解中的一个唯一的键值对。

2.9.18.1.element_value_pairs_info

字段名称 字段类型 字段说明
element_name_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个有效的字段描述符,这个字段描述符用于定义当前element_value_pairs的成员表示的注解的注解名
value element_value

2.9.18.2element_value

  1. 是一个可辨识联合体(Discriminated Union),用于表示“元素-值”的键值对中的值。
  2. 它被用来描述所有注解(包括RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations和untimeInvisibleParameterAnnotations)中涉及到的元素值。
字段名称 字段类型 字段说明
tag u1 表明了当前注解的元素-值对的类型[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AuqRGCIZ-1617353140580)(https://wiki.corp.qunar.com/confluence/download/attachments/436912037/image2021-3-24_11-8-19.png?version=1&modificationDate=1616555300000&api=v2)]
value value_info 表示当前注解元素的值。此项是一个联合体结构,当前项的tag项决定了这个联合体结构的哪一项会被使用

2.9.18.3.value_info

字段名称 字段类型 字段说明
const_value_index const_value_index_info
class_info_index u2 当tag项为’c’时,class_info_index项才会被使用。class_info_index项的值必须是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示返回描述符的类型,这个类型由当前element_value结构所表示的类型决定(譬如:'V’表示Void,'Ljava/lang/Object;'表示类java.lang.Object等)。
annotation_value annotation 当tag项为’@'时,annotation_value项才会被使用。这时element_value结构表示一个内部的注解(Nested Annotation)。
array_value array_value_info 当tag项为’['时,array_value项才会被使用

2.9.18.4.const_value_index_info

字段名称 字段类型 字段说明
type_name_index u2 对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个有效的字段描述符,这个字段描述符表示当前element_value结构所表示的枚举常量类型的内部形式的二进制名称
const_name_index u2 是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示一个有效的字段描述符,这个字段描述符表示当前element_value结构所表示的枚举常量类型的简单名称。

2.9.18.5array_value_info

字段名称 字段类型 字段说明
num_values u2 给定了当前element_value结构表示的数组类型的成员的数量。注意,允许数组类型元素中最多有65535个元素
values[num_values] element_value 每个成员的值都给指明了当前element_value 结构所表示的数组类型的一个元素值。

2.9.19.RuntimeInvisibleAnnotations

  1. RuntimeInvisibleAnnotations属性和RuntimeVisibleAnnotations属性相似,不同的是RuntimeVisibleAnnotations表示的注解不能被反射API访问,除非Java虚拟机通过特殊的实现相关的方式(譬如特定的命令行参数)收到才会(为反射的API)使用这些注解。否则,Java虚拟机将忽略RuntimeVisibleAnnotations属性。
  2. 是一个变长属性
  3. 位于ClassFile, field_info或method_info结构的属性表中。用于保存Java语言中的类、字段或方法的运行时的非可见注解。
  4. 每个ClassFile,field_info和method_info结构最多只能含有一个RuntimeInvisibleAnnotations属性,它为当前的程序元素保存所有的运行时非可见的Java语言注解。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“RuntimeInvisibleAnnotations”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
num_annotations u2 annotations[]数组的成员个数。
annotations[num_annotations] annotation annotations表的每个值表示程序元素上的单个运行时不可见注释。

2.9.20.RuntimeVisibleParameterAnnotations

  1. RuntimeVisibleParameterAnnotations属性是一个变长属性
  2. 位于method_info结构的属性表中。用于保存对应方法的参数的所有运行时可见Java语言注解。
  3. 每个method_info结构最多只能包含一个RuntimeVisibleParameterAnnotations属性,用于保存当前方法的参数的所有可见的Java语言注解。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“RuntimeVisibleParameterAnnotations”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
num_parameters u1 parameter_annotations[]数组的成员个数。
parameter_annotations[num_parameters] parameter_annotations_info 每个成员的值表示一个的参数的所有的运行时可见注解。它们的顺序和方法描述符表示的参数的顺序一致

2.9.20.1.parameter_annotations_info

字段名称 字段类型 字段说明
num_annotations u2 表示parameter_annotations[]数组在当前所索引处的元素的可见注解的数量
annotations[num_annotations] annotation 每个成员的值表示parameter_annotations[]数组在当前索引处的元素的一个唯一的可见注解

2.9.21.RuntimeInvisibleParameterAnnotations

  1. RuntimeInvisibleParameterAnnotations属性和RuntimeVisibleParameterAnnotations属性类似,区别是RuntimeInvisibleParameterAnnotations属性表示的注解不能被反射的API访问,除非Java虚拟机通过特殊的实现相关的方式(譬如特定的命令行参数)收到才会(为反射的API)使用这些注解。否则,Java虚拟机将忽略RuntimeInvisibleParameterAnnotations属性。
  2. RuntimeInvisibleParameterAnnotations属性是一个变长属性,
  3. 位于method_info结构的属性表中。用于保存对应方法的参数的所有运行时非可见的Java语言注解。
  4. 每个method_info结构最多只能含有一个RuntimeInvisibleParameterAnnotations属性用于保存当前Java语言中的程序元素的所有运行时不可见注解。
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“RuntimeInvisibleParameterAnnotations”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
num_parameters u1 parameter_annotations[]数组的成员个数。
parameter_annotations[num_parameters] parameter_annotations_info 每个成员的值表示一个的参数的所有的运行时不可见注解。它们的顺序和方法描述符表示的参数的顺序一致

2.9.22.AnnotationDefault

  1. AnnotationDefault属性是一个变长属性
  2. 位于某些特殊的method_info结构的属性表中,这些结构表示注解类型的元素。
  3. AnnotationDefault属性用于保存method_info结构表示的元素的默认值。每个method_info结构表示的一个元素的注解最多只能有一个AnnotationDefault属性
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“AnnotationDefault”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
default_value element_value 表示AnnotationDefault属性所对应注解类型元素的默认值

2.9.23.BootstrapMethods

  1. 是一个变长属性
  2. 位于ClassFile结构的属性表中。用于保存invokedynamic指令引用的引导方法限定符。
  3. 如果某个ClassFile结构的常量池中有至少一个CONSTANT_InvokeDynamic_info项,那么这个ClassFile结构的属性表中必须有一个明确的BootstrapMethods属性。ClassFile结构的属性表中最多只能有一个BootstrapMethods属性。
字段名称 字段类型 字段说明
字段名称 字段类型 字段说明
attribute_name_index u2 是一个对常量池的有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串“BootstrapMethods”。
attribute_length u4 当前属性的长度,不包括开始的6个字节。
num_bootstrap_methods u2 bootstrap_methods[]数组的成员个数。
bootstrap_methods[num_bootstrap_methods] bootstrap_methods_info 每个成员包含一个指向CONSTANT_MethodHandle结构的索引值,它代表了一个引导方法。还包含了这个引导方法静态参数的序列(可能为空)。

2.9.23.1.bootstrap_methods_info

字段名称 字段类型 字段说明
bootstrap_method_ref u2 是一个对常量池的有效索引。常量池在该索引处的值必须是一个CONSTANT_MethodHandle_info结构。注意:此CONSTANT_MethodHandle_info结构的reference_kind项应为值6(REF_invokeStatic)或8(REF_newInvokeSpecial),否则在invokedynamic指令解析调用点限定符时,引导方法会执行失败
num_bootstrap_arguments u2 bootstrap_arguments[]数组成员个数
bootstrap_arguments[num_bootstrap_arguments] u2 每个成员必须是一个对常量池的有效索引。常量池在该索引出必须是下列结构之一:CONSTANT_String_infoCONSTANT_Class_infoCONSTANT_Integer_infoCONSTANT_Long_infoCONSTANT_Float_infoCONSTANT_Double_infoCONSTANT_MethodHandle_infoCONSTANT_MethodType_info

由于篇幅的原因,在属性表章节中我并没有列出例子&进行分析例子,若小伙伴有需要,可以自己动手写一写,然后比对着这个文章&二进制文件进行查找验证。

3.总结

这篇文章通过简单的案例来给大家讲解Class文件具体是长啥样的,里面的二进制符合都代表啥含义;有了这个后我们再来看JVM是如何处理这个解析处理这个Class文件并进行执行的。具体内容欢迎看下一篇文章~

9999.参考资料

  1. JVM虚拟机规范:https://docs.oracle.com/javase/specs/jvms/se7/html/index.html
  2. 《深入理解JVM字节码》

你可能感兴趣的:(JVM,jvm)