Java虚拟机之类文件结构

1、概述

Java编译生成的class文件是运行在JVM虚拟机上的标准。

2. ClassFile

class文件是一组以8位字节为基础单位的二进制流。各个数据项目严格排序,没有任何分隔。遇到大于8位的情况,会分隔然后将高8位放在前面。class文件结构定义如下

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

class文件只有两种类型的数据:无符号数和表。

  1. 无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表 1、2、4、8 个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值、按照UTF8编码构成的字符串值。

  2. 表是由多个无符号数或其他表作为数据项构成的符合数据类型,所有的表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构数据。

  3. 整个class文件又下表所列出的数据项构成

当需要描述同一类型但数量不定的多个数据时,经常会在其前面使用一个前置的容量计数器来记录其数量,而便跟着若干个连续的数据项,称这一系列连续的某一类型的数据为某一类型的集合。Class 文件中各数据项是按照上表的顺序和数量被严格限定的,每个字节代表的含义、长度、先后顺序都不允许改变。

这里给出一个简单Class文件的示例,如下是有文本文件打开com.felix.clazz.MyClass.class文件如下:

cafe babe 0000 0034 0029 0700 0201 0017
636f 6d2f 6665 6c69 782f 636c 617a 7a2f
4d79 436c 6173 7307 0004 0100 106a 6176
612f 6c61 6e67 2f4f 626a 6563 7401 0004
6461 7461 0100 0149 0100 0373 7472 0100
124c 6a61 7661 2f6c 616e 672f 5374 7269
6e67 3b01 000d 436f 6e73 7461 6e74 5661
6c75 6508 000b 0100 0b57 6561 7468 6572
6669 7368 0100 063c 696e 6974 3e01 0003
2829 5601 0004 436f 6465 0a00 0300 100c
000c 000d 0900 0100 120c 0007 0008 0100
0f4c 696e 654e 756d 6265 7254 6162 6c65
0100 124c 6f63 616c 5661 7269 6162 6c65
5461 626c 6501 0004 7468 6973 0100 194c
636f 6d2f 6665 6c69 782f 636c 617a 7a2f
4d79 436c 6173 733b 0100 0364 6563 0100
1428 294c 6a61 7661 2f6c 616e 672f 5374
7269 6e67 3b07 001a 0100 176a 6176 612f
6c61 6e67 2f53 7472 696e 6742 7569 6c64
6572 0a00 1900 1c0c 000c 001d 0100 1528
4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b 2956 0900 0100 1f0c 0005 0006 0a00
1900 210c 0022 0023 0100 0661 7070 656e
6401 001c 2849 294c 6a61 7661 2f6c 616e
672f 5374 7269 6e67 4275 696c 6465 723b
0a00 1900 250c 0026 0018 0100 0874 6f53
7472 696e 6701 000a 536f 7572 6365 4669
6c65 0100 0c4d 7943 6c61 7373 2e6a 6176
6100 2100 0100 0300 0000 0200 0200 0500
0600 0000 1200 0700 0800 0100 0900 0000
0200 0a00 0200 0100 0c00 0d00 0100 0e00
0000 3d00 0200 0100 0000 0b2a b700 0f2a
120a b500 11b1 0000 0002 0013 0000 000e
0003 0000 000b 0004 000d 000a 000b 0014
0000 000c 0001 0000 000b 0015 0016 0000
0001 0017 0018 0001 000e 0000 003e 0003
0001 0000 0014 bb00 1959 120a b700 1b2a
b400 1eb6 0020 b600 24b0 0000 0002 0013
0000 0006 0001 0000 0010 0014 0000 000c
0001 0000 0014 0015 0016 0000 0001 0027
0000 0002 0028 

文件源码如下:

package com.felix.clazz;
public class MyClass {
    private int data;
    private final String str = "Weatherfish";
    public String dec() {
        return str + data;
    }
}

2.1 magic

每个Class文件头四个字节(u4)称为魔数(Magic Number),作用是确定这个文件是否为一个能被虚拟机接收的class文件。其魔数值为(OXCAFEBABE)。如上示例中,前四字节为cafe babe即为魔数。

2.2 minor_versionmajor_version

紧接着魔数的四字节存储的是class文件版本号,前两字节(u2)存储次版本号,后两字节(u2)存储主版本号;

虚拟机支持之前的版本(向下兼容)的class,但不支持之后的版本(向上兼容)的class。即虚拟机拒绝执行超过其版本的class文件。如示例中这四字节为0000 0034表示是有的JDK版本为1.8.

2.3 常量池

常量池由一个u2的大小的constant_pool_countcp_info类型的constant_pool[constant_pool_count-1]组成。

其中constant_pool_count是常量池入口。,代表常量池的容量计数值,也即代表了常量池的大小。

constant_pool[constant_pool_count-1] 代表实际的常量池,常量池是一个表(cp_info)类型的数据项。其索引是从1开始(有且只有这里从1开始计数)。索引0为表示某些指向常量池的索引值的数据在特定情况下需要表达“不引用任意一个常量池项目”的情况。 示例中的值为0029,表示常量池容量值为此两字节0x0029,即十进制的41,表示其有40个常量,索引范围是1-40

常量池主要存储字面量(如文本字符串,申明为final的常量值等)和符号引用(类和接口的全限定名,字段的名称和描述,方法的名称和描述)。

常量池中每一项常量都是一个表。这些表有一个共同特点:表开始的第一位是一个u1类型的标志位(tag,取值如下表),代表当前这个常量属于哪种常量类型。

常量池中数据项类型类型标志类型描述
CONSTANT_Utf8_info1UTF-8编码的Unicode字符串
CONSTANT_Integer_info3int类型字面值
CONSTANT_Float_info4float类型字面值
CONSTANT_Long _info5long类型字面值
CONSTANT_Double_info6double类型字面值
CONSTANT_Class_info7对一个类或接口的符号引用
CONSTANT_String_info8String类型字面值
CONSTANT_Fieldref_info9对一个字段的符号引用
CONSTANT_Methodref_info10对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref_info11对一个接口中声明的方法的符号引用
CONSTANT_NameAndType_info12对一个字段或方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

以上常量池表的14种常量类型各自均由自己的结构。具体结构说明可参考官方文档。以给出11种常用类型结构

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;//指向类全限定名常量项的索引
}

CONSTANT_Fieldref_info {
    u1 tag; //tag为标志位,前面已经给出
    u2 class_index; //指向申明字段的类或接口常量池描述符CONSTANT_Class_info的索引项
    u2 name_and_type_index;//指向字段常量池描述符CONSTANT_NameAndType_info的索引项
}

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;//指向申明字段的类或接口常量池描述符CONSTANT_Class_info的索引项
    u2 name_and_type_index;//指向字段常量池描述符CONSTANT_NameAndType_info的索引项
}

CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;//指向申明字段的类或接口常量池描述符CONSTANT_Class_info的索引项
    u2 name_and_type_index;//指向字段常量池描述符CONSTANT_NameAndType_info的索引项
}

CONSTANT_String_info {
    u1 tag;
    u2 string_index;//指向字符串常量值索引
}

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;   //大端(高位在前)存储的int
}

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;//大端(高位在前)存储的float
}

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;//大端(高位在前)存储的long的前4位
    u4 low_bytes;//大端(高位在前)存储的long的后4位
}

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;//大端(高位在前)存储的double的前4位
    u4 low_bytes;//大端(高位在前)存储的double的后4位
}

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;//指向该字段或方法名称常量项的索引
    u2 descriptor_index;//指向该字段或方法描述符常量项的索引
}

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;  //utf8编码的字符串占用的字节数
    u1 bytes[length];//utf8编码的字符串
}

CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}

CONSTANT_MethodType_info {
    u1 tag;
    u2 descriptor_index;
}

CONSTANT_InvokeDynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}

如示例中,常量池第一项为一字节的tag为07,查找常量类型表中类型为7的标志位CONSTANT_Class_info。这个类型的结构由两部分组成,第一部分就是一个u1大小的tag,前面已经分析,第二部分是一个u2大小的name_index,是一个常量地址索引值。参考示例中,name_index的值为0002,表示指向常量池第二个常量,即示例中的01(tag为1),即第二个常量的类型为CONSTANT_Utf8_info

CONSTANT_Utf8_info的结构由三部分组成

  1. 1个u1大小的tag
  2. 1个u2大小的length,表示UTf-8编码的字符串长度是多少字节
  3. length个u1大小的bytes组成。表示字符串的实际内容

参考示例中,length为0017,表示大小为十进制的23,其表示有21项常量,值为636f 6d2f 6665 6c69 782f 636c 617a 7a2f 4d79 436c 6173 73,这里的内容是UTF8编码的16进制数字,解码后的值即为com/felix/clazz/MyClass

这里写图片描述

可以使用命令javap -verbose MyClass 查看MyClass.class。其输出内容如下:

Classfile /Users/weatherfish/Documents/workspace/JVMTest/bin/com/felix/clazz/MyClass.class
  Last modified 2016-1-8; size 646 bytes
  MD5 checksum 663c8ce56836f2adcbaa58161572710e
  Compiled from "MyClass.java"
public class com.felix.clazz.MyClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/felix/clazz/MyClass
   #2 = Utf8               com/felix/clazz/MyClass
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               data
   #6 = Utf8               I
   #7 = Utf8               str
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               ConstantValue
  #10 = String             #11            // Weatherfish
  #11 = Utf8               Weatherfish
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Methodref          #3.#16         // java/lang/Object."<init>":()V
  #16 = NameAndType        #12:#13        // "<init>":()V
  #17 = Fieldref           #1.#18         // com/felix/clazz/MyClass.str:Ljava/lang/String;
  #18 = NameAndType        #7:#8          // str:Ljava/lang/String;
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               Lcom/felix/clazz/MyClass;
  #23 = Utf8               dec
  #24 = Utf8               ()Ljava/lang/String;
  #25 = Class              #26            // java/lang/StringBuilder
  #26 = Utf8               java/lang/StringBuilder
  #27 = Methodref          #25.#28        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #28 = NameAndType        #12:#29        // "<init>":(Ljava/lang/String;)V
  #29 = Utf8               (Ljava/lang/String;)V
  #30 = Fieldref           #1.#31         // com/felix/clazz/MyClass.data:I
  #31 = NameAndType        #5:#6          // data:I
  #32 = Methodref          #25.#33        // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #33 = NameAndType        #34:#35        // append:(I)Ljava/lang/StringBuilder;
  #34 = Utf8               append
  #35 = Utf8               (I)Ljava/lang/StringBuilder;
  #36 = Methodref          #25.#37        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #37 = NameAndType        #38:#24        // toString:()Ljava/lang/String;
  #38 = Utf8               toString
  #39 = Utf8               SourceFile
  #40 = Utf8               MyClass.java
{
  public com.felix.clazz.MyClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #15                 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #10                 // String Weatherfish
         7: putfield      #17                 // Field str:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 11: 0
        line 13: 4
        line 11: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/felix/clazz/MyClass;

  public java.lang.String dec();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #25                 // class java/lang/StringBuilder
         3: dup
         4: ldc           #10                 // String Weatherfish
         6: invokespecial #27                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
         9: aload_0
        10: getfield      #30                 // Field data:I
        13: invokevirtual #32                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        16: invokevirtual #36                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        19: areturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Lcom/felix/clazz/MyClass;
}
SourceFile: "MyClass.java"

2.4 类访问标志

常量池结束后,紧接着的两个字节(u2)表示访问标志(access_flags),这个标志用来识别一些类或者接口层次的访问信息。例如public类型、接口还是类、abstract标志、final标志等。其最终值为以上标志与的结果。

这里写图片描述

示例中,访问标志值为 0x0021,表示ACC_PUBLIC | ACC_SUPER,如下

这里写图片描述

2.5 索引

类索引、父类索引是一个u2类型的数据;接口索引集合是一组u2类型的数据的集合。以上三项用于确定继承关系。类/父类索引中存储的是类的全限定名的地址。除了Java.lang.Object的所有Java的父类索引都不能是0。

类索引/父类索引指向类型为CONSTANT_Class_info的类描述符常量。通过该常量中的索引值找到CONTANT_Utf8_info类型的常量,这个常量中存储的即为全限定名的字符串。

接口索引组合的第一项是一个u2的接口计数器(interfaces_count),表示索引表的容量,若无索引表则容量为0.接口索引组合第二项为第一项中数量个u2类型的容量。其中每一项索引指向类型为CONSTANT_Class_info的类描述符常量。通过该常量中的索引值找到CONTANT_Utf8_info类型的常量,这个常量中存储的即为全限定名的字符串。

示例中,类索引为0001,父类索引为 0003,接口索引容量为0000,接口索引表为空,不占容量。

2.6 字段表集合

字段表(field_info)集合用于描述接口或类中申明的变量。字段field:包括类级别的变量以及实例级别(static)的变量,但不包括方法内部申明的局部变量

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

字段表由两部分组成,第一项为u2大小的fields_count,表示字段数量计数器。第二部分为实际的各个字段内容,每个字段由一个字段表组成。以下表给出了字段表的格式:

这里写图片描述

其中

  1. access_flags是访问标志,下表给出具体值,说明见类访问标志部分
标志名称标志值含义
ACC_PUBLIC0x0001字段是否为 public
ACC_PRIVATE0x0002字段是否为 private
ACC_PROTECTED0x0004字段是否为 protected
ACC_STATIC0x0008字段是否为 static
ACC_FINAL0x0010字段是否为 final
ACC_VOLATILE0x0040字段是否为 volatile
ACC_TRANSIENT0x0080字段是否为 transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生的
ACC_ENUM0x4000字段是否为 enum

2. name_index,是对常量池的引用,代表字段的简单名称
3. descriptor_index,也是对常量池的引用,代表方法的描述符。其中描述符用来描述字段的数据类型,方法的参数列表(数量、类型、顺序)和返回值。

描述符标示符含义如下:

标示符对应类型
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
L全限定名ClassName;
Sshort
Zboolean
[一维数组

用描述符时,顺序为:先参数列表,然后返回值。其中参数列表必须按照严格的顺序放在()中。

字段表集合中不会出现从超类或接口中继承来的字段,但是有可能列出原本不存在的字段,比如内部类中会自动添加外部类的引用。

示例中,0002表示字段表的容量,这里表示有两个字段。接下来是第一个字段,第一个字段由五部分组成:

  1. 首先是u2大小的access_flag,存储的是属性字段的访问标志,值为0x0002,表示ACC_PRIVATE
  2. 接着是u2大小的name_index,存储的是属性字段的名字索引,值为0x0005,表示索引为5的常量,即为data
  3. 然后是u2大小的descriptor_index,存储的是属性类型描述在常量池中的索引,值为0x0006,表示索引为6的常量,即为I
  4. 其次是u2大小的attribute_count,这里存储的是第5项属性表集合的数量。值为0x0000,表示属性表集合数量为0,此时没有第5项。
  5. 最后是由第4项中控制大小的attribute_info,存储的是一个属性表集合,用于存储额外信息。

第二个字段的组成与第一个字段类似,区别在最后两项,以下重点分析。

  1. 前面三个字段值为 0x0012 0x0007 0x0008,分别表示ACC_PRIVATE|ACC_FINAL,str,Ljava/lang/String;.

  2. 第四项attribute_count 的值为0x0001,表示attribute_info中有一项。

  3. 第五项attribute_info根据第四项得知其有一项内容,属性表的结构比较灵活,具体属性表集合将会详细分析,这里只需要知道其包含一个ContentValue类型的属性,且属性值指向常量池中的字符串”Weatherfish”即可。

这里写图片描述

2.7 方法表集合

方法表与字段表类似,第一个字段为U2的计数器,代表方法的数量。后续为每个方法的方法表,其方法表结构等同于字段表结构的,可以参考字段表结构,这里不在重复。

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

方法的access_flags和字段的访问标志有部分差异,具体表如下:

标志名称标志值含义
ACC_PUBLIC0x0001方法是否为 public
ACC_PRIVATE0x0002方法是否为 private
ACC_PROTECTED0x0004方法是否为 protected
ACC_STATIC0x0008方法是否为 static
ACC_FINAL0x0010方法是否为 final
ACC_SYNCHRONIZED0x0020方法是否为 synchronized
ACC_BRIDGE0x0040方法是否由编译器产生的桥接方法
ACC_VRARGS0x0080方法是否为不定参数
ACC_NATIVE0x0100方法是为 native
ACC_ABSTRACT0x0400方法是为 abstract
ACC_STRICT0x0800方法是为 strictfp
ACC_SYNTHETIC0x1000方法是否由编译器自动产生的

而方法的代码经过编译器编译成字节码后存储在方法属性表集合中的一个名为Code的属性里面。此部分详细内容将在属性表集合详细说明。

父类的方法如果在子类中没有重写,方法表集合中就不会出现。方法表可能会出现编译器加入的方法,比如类构造器<clinit>和实例构造器<init>

方法重载,是根据方法的特征签名标示的,特征签名是一个方法中各个参数在常量池中的字段符号引用的集合。这里不包含返回值。故Java代码中方法重载是不能以返回值来区分的。但是class文件中其实是允许存在此种情况的。

示例中,方法表第一个u2类型的数据,表示方法表的数量。当前值为0x0002,表示有两个方法。方法表同字段表由五部分组成,以下给出第一个方法的对应分析:

  1. 首先是u2大小的access_flag,存储的是方法的访问标志,值为0x0001,表示ACC_PUBLIC
  2. 接着是u2大小的name_index,存储的是方法的名字索引,值为0x000C,表示索引为12的常量池引用,即为<init>
  3. 然后是u2大小的descriptor_index,存储的是方法类型描述在常量池中的索引,值为0x000D,表示索引为13的常量,即为()V
  4. 其次是u2大小的attribute_count,这里存储的是第5项属性表集合的数量。值为0x0001,表示属性表集合数量为1。
  5. 最后是由第4项中控制大小的attribute_info,存储的是一个属性表集合,这里只有一项。代表的是Code属性。具体解析部分见下面属性表集合。

这里写图片描述

2.8 属性表集合

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

在Class文件,字段表,方法表中都可以包含属性表集合attribute_info,用于描述某些场景专有的信息。对于每个属性,它的前两项是固定不变的,即第一项attribute_name_index表示常量池中一个CONTANT_Utf8_info类型的引用;然后第二项是一个u4的attribute_length,表示后面属性值的长度。 属性值部分完全是自定义的。

2.8.1 ConstantValue

ConstantValue属性作用于字段表,表示此字段是static定义的常量值。其作用是通知虚拟机自动为静态变量赋值。只有被static(ACC_STATIC)修饰的类变量才会使用这项属性。若是非static的属性包含了ContentValue属性,虚拟机会直接忽略。

ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;//代表常量池中的一个常量的引用,这个常量的限制下面给出。
}

constantvalue_index 可以指向CONSTANT_Long_info,CONSTANT_Float_info,CONSTANT_Double_info,CONSTANT_Integer_info,CONSTANT_String_info,这几种常量引用中的一种。

对于非 static 类型的实例变量的赋值是在对象初始化方法方法中进行的;

对于 static 的类变量,有两种方式实现赋值:

  1. 对于使用final static (ACC_FINAL | ACC_STATIC) 修饰的基本类型或String类型的类常量,使用ContentValue初始化;
  2. 其他static修饰的属性在类构造器方法中初始化;
2.8.2 Code

Code属性作用于方法表,Java 程序方法体中的代码经过 javac 编译后,生成的字节码指令便会存储在 Code 属性中,但并非所有的方法表都必须存在这个属性,比如接口或抽象类中的方法就不存在 Code 属性。如果方法表有 Code 属性存在,那么它的结构将如下表所示:

这里写图片描述

Code表的数据结构如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
  1. attribute_name_index 是一项指向 CONSTANT_Utf8_info 型常量的索引,常量值固定为 “Code”,它代表了该属性的名称。
  2. attribute_length 指示了属性值的长度,由于属性名称索引与属性长度一共是 6 个字节,所以属性值的长度固定为整个属性表的长度减去 6 个字节。
  3. max_stack 代表了操作数栈深度的最大值
  4. max_locals 代表了局部变量表所需的存储空间,它的单位是Slot。其大小跟局部变量数量没有直接关系,因为局部变量表中的 Slot 可以重用。方法参数(包括this)、显式异常处理的参数(catch语句块所定义的异常)、方法体中定义的局部变量都存储在局部变量表。


    javac在编译的时候会讲this关键字的访问转变为对一个普通参数方法的访问,然后在虚拟机调用实 例方法的时候自动传入此参数。


  5. code_length 字节码code的长度。虽然是一个 u4 类型的长度值,但是虚拟机规范中限制了一个方法不允许超过 65535 条字节码指令,如果超过了这个限制,Javac 编译器将会拒绝编译。

  6. code 用于存储字节码指令的一系列字节流,它是 u1 类型的单字节,因此取值范围为 0x00 到 0xFF,那么一共可以表达 256 条指令,详细请查看Java虚拟机之指令集.
  7. 字节码指令之后是方法的显式异常处理表集合(exception_table),它对于 Code 属性来说并不是必须存在的。它的格式如下表所示:

    类型名称数量
    U2start_pc1
    U2end_pc1
    U2handler_pc1
    U2catch_type1

    它包含四个字段,这些字段的含义为:如果字节码从第 start_pc 行到第 end_pc 行之间(不含
    end_pc 行)出现了类型为 catch_type 或其子类的异常(catch_type为指向一个
    CONSTANT_Class_info 型常量的索引),则转到第 handler_pc 行继续处理,当
    catch_type 的值为 0 时,代表任何的异常情况都要转到 handler_pc 处进行处理。

    异常表实际上是 Java 代码的一部分,编译器使用异常表而不是简单的跳转命令来实现 Java 异常即
    finally 处理机制,也因此,***finally 中的内容会在 try 或 catch 中的 return 语句之前
    执行,并且在 try 或 catch 跳转到 finally 之前,会将其内部需要返回的变量的值复制一份副本
    到最后一个本地表量表的 Slot中*

在前面方法表的示例中,已经分析了Code属性的出现,这里继续分析Code的内容(Code的第一个方法是编译器生成的):

  1. 首先是一个u2的attribute_name_index,存储的是一个指向CONSTANT_Uft8_info型的常量池常量,这里值为0x000e,代表Code
  2. 第二项是u4的attribute_length,存储的是后面属性的长度,这里是0x0000003D,表示后续的属于此Code的还有51字节。
  3. u2大小的max_stack,存储的是操作数栈最大深度。这里是0x0002,代表最大深度为2.
  4. u2大小的max_locals,存储的是局部变量所需的存储空间,这里是0x0001,代表需要1个Slot。
  5. u4大小的code_length,存储的是下一字段code的长度,这里是0x0000000B,代表下一字段长度为11字节。
  6. 上一项中code_length指定长度的code,存储的是编译后的字节码。这里为0x2AB7000F2A120AB50011B1,虚拟机指令执行部分,参考Java虚拟机之字节码执行引擎
  7. 接着是u2的exception_table_length,存储的是下一项异常表的个数,这里是0x0000,表示异常表位空。
  8. 接着是exception_table,由于第7项中,记录的个数为0,故这里没有异常表,也不占用空间。
  9. 然后是u2的attributes_count,存储的是其他attributes属性的大小。这里是0x0002,表示有两项。
  10. 最后是9中定义数量的attributes,存储的是其他属性信息。也就是另一个属性表。这里在后面LineNumberTable会继续分析。

这里写图片描述

2.8.3 StackMapTable

StackMapTable属性是JDK1.6添加的,位于Code属性表中的属性。这个属性会在虚拟机类加载的字节码验证阶段被新的类型检查器(Type Checker)使用。目的是为代替之前比较消耗性能的基于数据流分析的类型推导验证器。

新的类型检查器在同样保证Class文件合法性的前提下,省略了再运行时通过数据流分析去确认字节码的行为逻辑合法性的步骤,而是在编译阶段将一系列的验证类型(Verification Types)直接记录在class文件中,通过检查这些验证类型代替了类型推导过程,提高了性能。

其数据结构如下:

StackMapTable_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              number_of_entries; //stack_map_frame的大小
    stack_map_frame entries[number_of_entries];//方法的stack_map_frame结构数组
}

版本号大于50的class文件中,若方法属性中没有附带StackMapTable属性,则意味着其带有一个默认的隐藏式StackMap属性。这个StackMap属性作用等同于number_of_entries值为0的StackMapTable属性。一个方法的Code属性最多只能带有一个StackMapTable属性,否则会报ClassFormatError异常

StackMapTable中包含零到多个栈映射帧(stack_map_frame),每个栈映射帧都显式或隐式的代表了一个字节码偏移量,用于表示该执行方法的局部变量和操作数栈的验证类型。类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。其结构如下

union stack_map_frame {
    same_frame;
    same_locals_1_stack_item_frame;
    same_locals_1_stack_item_frame_extended;
    chop_frame;
    same_frame_extended;
    append_frame;
    full_frame;
}

stack_map_frame中,并不是直接记录了字节码的索引值,而是记录了offset_delta的值。stack_map_frame中的每一项都通过(前一项的值 + 1 + offset_delta)计算出当前项对应的真正的字节码的位置,只有当当前stack_map_frame的前一项是当前方法的初始帧(initial frame of the method)的时候,offset_delta的值直接表示字节码位置。

其中,union中各项的含义说明如下:

  1. same_frame

    same_frame {
        u1 frame_type = SAME; /* 0-63 */
    }

    当前栈帧的值,即tag的值为[0-63],offset_delta = frame_type。表示当前帧和前一帧有相同的局部变量,并且当前操作数栈为空。

  2. same_locals_1_stack_item_frame

    same_locals_1_stack_item_frame {
        u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */
        verification_type_info stack[1];
    }

    tag值为[64-127],offset_delta = frame_type – 64。表示当前帧和前一帧有相同的局部变量,并且操作栈内的操作数条目数为1,因而它为该操作数栈内的操作数保存了一项verification_type_info(详见8).Tags [128-246]保留.

  3. same_locals_1_stack_item_frame_extended

    same_locals_1_stack_item_frame_extended {
        u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED; /* 247 */
        u2 offset_delta;
        verification_type_info stack[1];
    }

    tag值为247,offset_delta = offset_delta(结构中给定值)。表示当前值帧和前一帧
    有相同的局部变量,并且操作栈内的操作数条目数为1,因而它为该操作数栈内的操作数保存了一项verification_type_info(详见8)。和上一类型的区别是这里的offset_delta是直接给出的

  4. chop_frame

    chop_frame {
        u1 frame_type = CHOP; /* 248-250 */
        u2 offset_delta;
    }

    tag值为[248, 250]。offset_delta = offset_delta。表示当前操作栈为空,而当前局部变量比前一帧的局部变量少了后面的251 – frame_type个局部变量。

  5. same_frame_extended

    same_frame_extended {
        u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
        u2 offset_delta;
    }

    tag值为251。offset_delta = offset_delta(后者为结构中给出)。表示当前帧和前一帧有相同的局部变量,并且操作数栈为空。samep_frame的区别是same_frame_extended中的offset_delta值直接给出

  6. append_frame

    append_frame {
        u1 frame_type = APPEND; /* 252-254 */
        u2 offset_delta;
        verification_type_info locals[frame_type - 251];
    }

    tag值为[252-254]。offset_delta = offset_delta(后者为结构中给出)。表示操作数栈为空,而当前帧的局部变量比前一帧的局部变量多了frame_type – 251个。因而它也定义了frame_type – 251项的verification_type_info类型。详见8。

    locals[0]表示0号局部变量。若locals[M]为第N个变量,当locals[M]为以下类型时,则locals[M+1]为第N+1个变量:

    Top_variable_info
    Integer_variable_info
    Float_variable_info
    Null_variable_info
    UninitializedThis_variable_info
    Object_variable_info
    Uninitialized_variable_info

    其他情况时,locals[M+1]为第N+2个变量.以上属性将在第8项详细解析。

  7. full_frame

    full_frame {
        u1 frame_type = FULL_FRAME; /* 255 */
        u2 offset_delta;
        u2 number_of_locals;
        verification_type_info locals[number_of_locals];
        u2 number_of_stack_items;
        verification_type_info stack[number_of_stack_items];
    }

    tag值255。offset_delta = offset_delta(后者为结构中给出)。full_frame定义了所有的信息,包括offset_delta的值,以及当前帧和前一帧不同的所有局部变量和操作数。

    locals[0]表示0号局部变量。若locals[M]为第N个变量,当locals[M]为以下类型时,则locals[M+1]为第N+1个变量:

    Top_variable_info
    Integer_variable_info
    Float_variable_info
    Null_variable_info
    UninitializedThis_variable_info
    Object_variable_info
    Uninitialized_variable_info

    其他情况时,locals[M+1]为第N+2个变量.以上属性将在第8项详细解析。

    stack[0]表示栈底操作数。若将栈底做为0号变量,变量编号向栈顶方向递增。此时若stack[M]为第N个变量,当stack[M]为以下类型时,则stack[M+1]为第N+1个变量。

    Top_variable_info
    Integer_variable_info
    Float_variable_info
    Null_variable_info
    UninitializedThis_variable_info
    Object_variable_info
    Uninitialized_variable_info

    其他情况时,stack[M+1]为第N+2个变量.以上属性将在第8项详细解析。

  8. verification_type_info

    union verification_type_info {
        Top_variable_info;
        Integer_variable_info;
        Float_variable_info;
        Long_variable_info;
        Double_variable_info;
        Null_variable_info;
        UninitializedThis_variable_info;
        Object_variable_info;
        Uninitialized_variable_info;
    }

    verification_type_info 记录字节码的验证类型。每一项第一个字节指定验证类型(tag),其各项的具体含义如下:

    1. Top_variable_info

      指定局部变量的验证类型为top

      Top_variable_info {
          u1 tag = ITEM_Top; /* 0 */
      }
    2. Integer_variable_info

      指定局部变量的验证类型为int

      Integer_variable_info {
          u1 tag = ITEM_Integer; /* 1 */
      }
    3. Float_variable_info

      指定局部变量的验证类型为float

      Float_variable_info {
          u1 tag = ITEM_Float; /* 2 */
      }
    4. Long_variable_info

      指定局部变量的验证类型为long,此结构定义了两个本地变量数组变量或操作数栈变量。

      若是本地变量则:他不能是最后一个变量(索引最大的变量);他的下一个变量(当前索引后面的变量)包含verification type top

      若是操作数栈变量则:他不能是当前栈的栈顶;他的下一个变量(往栈顶方向)包含verification type top

      Long_variable_info {
          u1 tag = ITEM_Long; /* 4 */
      }
    5. Double_variable_info

      指定局部变量的验证类型为double。同4

      Double_variable_info {
          u1 tag = ITEM_Double; /* 3 */
      }
    6. Null_variable_info

      指定局部变量的验证类型为null

      Null_variable_info {
          u1 tag = ITEM_Null; /* 5 */
      }
    7. UninitializedThis_variable_info

      指定验证类型为uninitializedThis

      UninitializedThis_variable_info {
          u1 tag = ITEM_UninitializedThis; /* 6 */
      }
    8. Object_variable_info

      指定验证类型为cpool_index中指定的存在于常量池中的CONSTANT_Class_info类型实例。

      Object_variable_info {
          u1 tag = ITEM_Object; /* 7 */
          u2 cpool_index;
      }
    9. Uninitialized_variable_info

      指定验证类型为uninitialized(offset), 其中offset记录了用于创建实例的new指令的偏移量。

      Uninitialized_variable_info {
          u1 tag = ITEM_Uninitialized /* 8 */
          u2 offset;
      }

由于原示例未包含此部分,故这里新增一个示例来分析此部分功能。

2.8.4 Exceptions

Exceptions属性的作用是列举出方法中可能抛出的的受检查的异常。也即方法描述时在throws关键字后面列举出的异常。以下给出其属性表结构

类型名称数量说明
U2attribute_name_index1前两项是固定不变的不再分析
U4attribute_length1
U2number_of_exceptions1抛出异常数量
U2exception_index_tablenumber_of_exceptions指向CONTANT_Class_info代表受查异常类型

数据结构如下

Exceptions_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_exceptions;
    u2 exception_index_table[number_of_exceptions];
}

一个方法只有在以下三种情况下才会抛出 exception(只是编译时必须,而不是JVM强制的)
1. 这个异常时 RuntimeException或者其子类.
2. 这个异常是 Error或者其子类。
3. 这个异常时在exception_index_table定义的异常,或者其子类。

3.8.5 InnerClasses

InnerClasses属性用于记录内部类与宿主类之间的关联。编译器会将为内部类和外部类都生成此结构.其数据结构如下:

InnerClasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;   //代表需要记录多少个内部类信息
    {   u2 inner_class_info_index;//指向CONSTANT_Class_info的内部类符号引用索引
        u2 outer_class_info_index;//指向CONSTANT_Class_info的外部类符号引用索引
        u2 inner_name_index;//指向CONSTANT_Utf8_info的内部类名称索引,匿名内部类为0
        u2 inner_class_access_flags;//内部类访问标示,下面分析。
    } classes[number_of_classes];
}

其中 inner_class_access_flags包含以下访问标识。关于访问标示的具体说明可以参考前面分析。

属性名描述说明
ACC_PUBLIC0x0001public
ACC_PRIVATE0x0002private
ACC_PROTECTED0x0004protected
ACC_STATIC0x0008static
ACC_FINAL0x0010final
ACC_INTERFACE0x0200interface
ACC_ABSTRACT0x0400abstract
ACC_SYNTHETIC0x1000synthetic(隐藏标示)
ACC_ANNOTATION0x2000annotation
ACC_ENUM0x4000enum
3.8.6 EnclosingMethod

EnclosingMethod是一个固定长度的可选属性。当且仅当一个class是一个局部类(local class,也就是方法内部定义的类)或者匿名类(anonymous class),这个类才有EnclosingMethod这个属性,而且一个类只允许有一个EnclosingMethod属性。其数据结构如下:

EnclosingMethod_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 class_index;//指向常量池CONSTANT_Class_info类型的索引,表示其所在方法的宿主类。
    u2 method_index;
}

method_index 若当前类没有被方法包含,如当前类是赋值给类成员的匿名类,则method_index值为0,否则该method_index的值为constant_pool中的索引CONSTANT_NameAndType_info类型。记录了class_index指定的类中定义的包含当前类的方法名和类型信息。

3.8.7 Synthetic

Synthetic属性是由编译器添加的,而非源码指定。除了<clinit><init>,所有由非用户代码生成的类/方法/字段,都必须包含以下两项中的一项:设置Synthetic属性和包含ACC_SYNTHETIC标志位

Deprecated_attribute {
    u2 attribute_name_index;
    u4 attribute_length;    //必须为0
}
3.8.8 Signature

推测Signature Attribute存在于当前Signature Attribute所在类型是泛型(泛型类、泛型方法、泛型字段)的时候。它和field_infomethod_infothis_class一起对应于局部变量中的LocalVariableTable Attribute和LocalVariableTypeTable Attribute,他们同时都有descriptor版本和signature版本。

Signature_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 signature_index;
}

signature_index 是常量池中CONSTANT_Utf8_info类型索引。记录当前类型的签名(类签名、字段签名、方法签名)。签名信息并不是给JVM用的,而是用于编译、调试、反射。

  1. 类签名

    语法定义:

    1. ClassSignature:

      FormalTypeParametersopt SuperclassSignature SuperinterfaceSignature*

    2. FormalTypeParameters:

3.8.9 SourceFile

SourceFile属性用于记录生成这个Class文件的源码文件名称,这个属性是一个定长属性,结构如下:

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;    //源文件名,指向`Contant_Utf8_info`型索引
}
3.8.10 SourceDebugExtension

SourceDebugExtension是classfile文件可选的结构,一个class文件对多只能有一个SourceDebugExtension属性。且数据结构如下

SourceDebugExtension_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 debug_extension[attribute_length];//Utf8格式的字符串记录扩展调试信息,不以0结尾
}
3.8.11 LineNumberTable

LineNumber属性用于描述Java源码行号与字节码行号(字节码偏移量)之间的对应关系。

类型名称数量
U2attribute_name_index1
U4attribute_length1
U2line_number_table_length1
line_number_infoline_number_tableline_number_table_length

其数据结构如下:

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];
}

其中,line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合。

line_number_info表包括了start_pcline_number两个U2类型的数据项,前者是字节码行号,后者是Java源码行号。

示例中,Code属性就包含了此属性,(这里分析的为方法),以下详细分析。

这里写图片描述

  1. 首先获取其attribute_name_index,值为0x0013, 对应常量池值为LineNumberTable
  2. 获取其attribute_length,值为0x0000000E,对应大小为14。表示此属性后续字节为14.
  3. 获取line_number_table_length,此处存储的是表的大小,值为0x0003。表示有三个指令源码对应表。
  4. 第一个对应表start_pc存储的是指令开始的索引,值为0x0000,line_number存储源码行号,值为0x000B,表示代码0,行号11.
  5. 同理可以获取第二行0x0004,0x000D以及第三行0x000A,0X000B.
  6. 下一项是在LocalVariableTable分析

这里写图片描述

3.8.12 LocalVariableTable

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。

其数据结构:

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}

结构说明如下:

类型名称数量
U2attribute_name_index1
U4attribute_length1
U2local_variable_table_length1
local_variable_infolocal_variable_tablelocal_variable_table_info

local_variable_info项目代表了一个栈帧与源码中的局部变量的关联,结构如下:

类型名称数量说明
U2start_pc1这个局部变量生命周期开始的字节码偏移量
U2length1生命周期作用范围[start_pc, start_pc+length)
U2name_index1局部变量的名称,指向Contant_Utf8_info型索引
U2descriptor_index1局部变量的字段描述符,指向Contant_Utf8_info型索引
U2index1这个局部变量在局部变量表中Slot的位置(long/double的局部变量占两个Slot)

示例中,接着LineNumberTable的根据其tag值为0x0014,对应常量池LocalVariableTable。

然后LocalVariableTable读取第二项,其长度为0x0000000C.第三项local_variable_table_length为0x0001,表示一个local_variable_info.

local_variable_info中继续读取,第一项start_pc为0000,length为0X000B,name_index为0x0015,代表变量池的this,descriptor_index为0x0016,代表Lcom/felix/clazz/MyClass;最后读取index为0x0000.即Slot为0.至此,Code的第一个方法分析完毕。后续的字节码为第二个方法表的开始,这里不再具体分析。

这里写图片描述

3.8.13 LocalVariableTypeTable

数据结构如下:

LocalVariableTypeTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_type_table_length;
    {   u2 start_pc;//同上LocalVariableTable
        u2 length;  //同上LocalVariableTable
        u2 name_index;//同上LocalVariableTable
        u2 signature_index;//局部变量字段签名,指向`Contant_Utf8_info`型索引
        u2 index;//记录该项代表的局部变量在方法的局部变量数组中的索引
    } local_variable_type_table[local_variable_type_table_length];
}

Java1.5为泛型新增属性,同2.8.4中LocalVariableTable属性,只是将记录的字段描述符descriptor_index替换成字段的特征签名,对于非泛型的类型,描述符和特征签名能描述的信息基本一致;对于泛型,由于描述符中的泛型的参数化类型被擦除掉,故使用LocalVariableTypeTable属性标记带泛型的局部变量描述符。

local_variable_type_table每项记录了一个泛型局部变量值的范围和该局部变量在局部变量数组中的索引值。

3.8.14 Deprecated

布尔型属性,在源码中使用@deprecated注解进行设置,其数据结构:

Deprecated_attribute {
    u2 attribute_name_index;
    u4 attribute_length;    //必须为0
}
3.8.15 RuntimeVisibleAnnotations

RuntimeVisibleAnnotations一般用于ClassFile, field_info, or method_info等结构,他记录了Java运行时corresponding class, field, method中可见的annotation.

ClassFile, field_info, and method_info每个结构中最多包含一个RuntimeVisibleAnnotations,

RuntimeVisibleAnnotations_attribute {
    u2         attribute_name_index;
    u4         attribute_length;
    u2         num_annotations;//运行时可见的annotations数量,最大65535个
    annotation annotations[num_annotations];记录每一个运行时可见annotations。
}

annotation记录了每个运行时注解的信息,其结构如下:

annotation {
    u2 type_index;//常量池CONSTANT_Utf8_info类型索引,表示annotation typea
    u2 num_element_value_pairs;//element_value_pairs数组大小
    {   u2  element_name_index;//常量池中CONSTANT_Utf8_info类型索引,表示annotation type
        element_value value;//表示annotation type的值
    } element_value_pairs[num_element_value_pairs];
}

element_value用于描述annotation,包括RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, and RuntimeInvisibleParameterAnnotations都是使用此union描述其结构的。

element_value {
    u1 tag; //annotation type
    union { //union记录当前annotaion键值对中的值。

        //tag是B, C, D, F, I, J, S, Z, or s时使用,值是常量池中tag对于类型的值.
        u2 const_value_index;

        {
   //常量池CONSTANT_Utf8_info索引。记录二进制名(binary name,以descriptor的形式表示)
           u2 type_name_index;
    //常量池中CONSTANT_Utf8_info索引,记录当前枚举类型的值(枚举类型内部成员字符串)
           u2 const_name_index;

        } enum_const_value; //tag是 e 时使用,记录枚举类型值。

    //tag是c时使用,常量池中CONSTANT_Utf8_info索引,以descriptor记录当前值所表达的Class类型返回值。
        u2 class_info_index;

        annotation annotation_value;    // tag是@时使用,代表一个内嵌的annotation.

        {   u2  num_values;//数组元素个数,最大65535
            element_value values[num_values];//数组值的类型
        } array_value;//tag 是[的时候使用
    } value;
}

其中,tag为annotation type。其取值说明如下:

tagElement Type
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
L全限定名ClassName;
Sshort
Zboolean
sString
eenum constant
c(小)class
@annotation type
[array
3.8.16 RuntimeInvisibleAnnotations

RuntimeInvisibleAnnotations一般用于ClassFile, field_info, or method_info等结构,他记录了Java运行时corresponding class, field, method中不可见的annotation.

ClassFile, field_info, and method_info每个结构中最多包含一个RuntimeInvisibleAnnotations,

RuntimeInvisibleAnnotations_attribute {
    u2         attribute_name_index;
    u4         attribute_length;
    u2         num_annotations;//同3.8.15
    annotation annotations[num_annotations];//同3.8.15
}
3.8.17 RuntimeVisibleParameterAnnotations

RuntimeVisibleParameterAnnotations与RuntimeVisibleAnnotations 不同在于,前者只能用于记录method_info的方法参数在运行时可见的annotation信息。

RuntimeVisibleParameterAnnotations_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 num_parameters;//记录该方法中参数个数
    {   u2         num_annotations; //每个参数中annotation个数
        annotation annotations[num_annotations];//参数值
    } parameter_annotations[num_parameters];
}
3.8.18 RuntimeInvisibleParameterAnnotations

RuntimeInvisibleParameterAnnotations与RuntimeVisibleParameterAnnotations区别是,前者记录的是方法参数在运行时不可见的annotation信息

RuntimeInvisibleParameterAnnotations_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 num_parameters;
    {   u2         num_annotations;
        annotation annotations[num_annotations];
    } parameter_annotations[num_parameters];
}
3.8.19 AnnotationDefault

AnnotationDefault Attribute用于Annotation类型方法中,以记录该方法所代表的Annotation类型的默认值。每个Annotation类型的method_info中的attributes中只能包含一个AnnotationDefault Attribute项。

AnnotationDefault_attribute {
    u2            attribute_name_index;
    u4            attribute_length;
    element_value default_value;//记录该方法表示的Annotation类型的默认值。
}
3.8.20 BootstrapMethods

BootstrapMethods是JDK1.7以后加入的属性,这个属性用于保存invokedynamic指令引用的引导方法限定符。一个类如果出现了CONSTANT_InvokeDynamic_info属性,则其所在类必须有且仅有一个BootstrapMethods属性。

BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;//下面数组的大小
    {   u2 bootstrap_method_ref;//常量池中Constant_MethodHandler_info的索引
        u2 num_bootstrap_arguments;//下面数组的大小
        u2 bootstrap_arguments[num_bootstrap_arguments];
        //常量池索引:String/Class/Integer/Long/Float/Double/MethodHandle/MethodType类型的常量
    } bootstrap_methods[num_bootstrap_methods];
}

4 整个ClassFile结构图

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值