Java虚拟机学习笔记(-)--- Class文件结构

346 阅读22分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

前言

最近在学习java 虚拟机相关知识,主要是阅读了《深入理解Java虚拟机》这本书,结合官网虚拟机规范文档,记录下的笔记

参考文档

  1. Class 文件结构
  2. class 字节码规范 jdk7
  3. class 字节码规范 jdk8
  4. 《深入理解Java虚拟机》

理解Java虚拟机-- Class文件结构(JDK8)

JVM 是什么

Java 虚拟机 JVM 识别的是Java编译后的class文件,而不是Java语言源码,Java语言只是它的主要服务对象。事实上任何编程语言,只有“编译”成JVM可识别的class文件,就能在jvm上运行。

Class 字节码表示

为了节约字节码空间,class文件不采用分割符之类来分割数据,而是定义好各个部分的排序。

比如

  1. 魔术 占用四个字节

  2. 次版本号 一个字节

  3. 主版本号 一个字节

  4. 常量池 数量, 两个字节

  5. 类名 用两部分表示

(1) 第一部分一个字节,表示有多少个类名表示字节

(2)第二部分

  1. 属性 两部分

    (1) 第一个字节 记录有多少个属性

    (2 ) 记录每个属性,属性第一个字节表示属性占用的字节,之后是属性的内容。

等这种结构。

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
    
    u2             fields_count;// 字段个数
    field_info     fields[fields_count];// 字段数组,每个属性都有
    u2             methods_count;//方法数
    method_info    methods[methods_count];//方法数组 
    u2             attributes_count;// 属性
    attribute_info attributes[attributes_count];//属性数组
}

如上是java class 的字节结构,其中 u数字,表示的是占用的字节数,比如u4表示占用4个字节数。class 字节码中都是一个个的二进数,我们可以用 javap命令 将其转换为文本描述内容。

javap -v Helloc.class

image-20210711105857690

图片来自<<深入理解Java 虚拟机>>

1.1 class 文件魔数与版本号

魔数

首先是魔数,其占用了四个字节,魔数的唯一作用是确定这个文件是否为一个能被虚拟机接受的class。很多文件存储都用魔数头识别,比如 jpeg图片等。

文本版本号

其次是次版本号和主版本号,用于表示class 文件的版本号.高版本的JVM 可以运行兼容运行低版本的class,反之则不行。高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文 件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

1.2 Constant Pool (常量池)

常量池个数

class 文件中 使用 两个字节 用于表示常量池的数量。从后面的描述中我们可以到实际数组大小只有 constant_pool_count-1,因为这个容量计数是从1开始的。设计者将0空出来,满足后面某些指向常量池的索引值的数据在特定情况下表达”不引用任何一个常量池"的含义。 不过,Class 文件结构中也只有常量池的容量计数是从1开始,其他集合类型都是从0开始的。

常量的基本结构

常量的信息如下

cp_info {
    u1 tag;
    u1 info[];
}

一个常量首先有一个字节,tag,用于表示该常量的类型,tag 对应的值如下。

常量类型表格

Constant TypeValue描述
CONSTANT_Utf81UTF-8 编码的字符串
CONSTANT_Integer3int 型字面量
CONSTANT_Float4Float 型 字面量
CONSTANT_Long5Long 型 字面量
CONSTANT_Double6Double 型 字面量
CONSTANT_Class7类或接口的 符号引用
CONSTANT_String8字符串类型 字面量
CONSTANT_Fieldref9字段的符号引用
CONSTANT_Methodref10类中方法的 符号引用
CONSTANT_InterfaceMethodref11接口方法的 符号引用
CONSTANT_NameAndType12字段或方法的部分符号引用
CONSTANT_MethodHandle15表示方法的句柄
CONSTANT_MethodType16标识方法类型
CONSTANT_InvokeDynamic18表示一个动态方法调用点

而 info[] 的信息则根据tag 的不同而各种各样。每个常量池都有其自己的结构

常量池内容

常量池中主要存放两大类常量: 字面量(Literal)符合引用(Symbolic References).

  • 字面量 字面量接近于Java 语言中的常量概念,如文本字符串,声明为final的常量值等。

  • 符号引用 其属于编译原理方面的概念.其包括三类常量:

    类和接口的全限定名(Fully Qualified Name)(全限定名是指包括类的完全路径,如 java.utils.List)

    字段的名称和描述符(Descriptor)

    方法的名称和描述符

为什么常量池有这么多种常量?

​ Java 代码在进行Javac 编译时,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class 文件中不会保存各个方法、字段的最终内存布局信息。因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析,翻译到具体的内存地址之中。

常量池中每一项常量都是一个表, 在Jdk1.7之前有11种,jdk1.7之后Java为了更好地支持动态语言的调用,增加了

CONSTANT_MethodHandleCONSTANT_MethodTypeCONSTANT_InvokeDynamic 三种类型。

以上,引用自<<深入理解Java虚拟机>>

CONSTANT_Utf8

CONSTANT_Utf8 用于表示一个常量字符串的值。 基本上,一个class文件中缺什么常量都不可能缺 CONSTANT_Utf8,至少类的名称就需要用一个 CONSTANT_Utf8 常量类表示。

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

字面量

比如 CONSTANT_IntegerCONSTANT_FLOAT ,tag 表示类型,再用4个字节表示对应的数据。而Long、Double 则是 8个字节

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_String_info 下, string_index 是指向常量池表 中的索引,而常量池中对应用的必定是 CONSTANT_Utf8 类型的数据

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

符号引用

  • CONSTANT_Class_info 此类型的常量代表一个类或者接口的符号引用。

    CONSTANT_Class_info {
        u1 tag;
        u2 name_index;
    }
    

    name_index :是一个索引值,指向常量池中一个CONSTANT_Utf8 类型的常量,此常量代表了这个类(或接口)的全限定名称。

  • CONSTANT_NameAndType_info

    用于表示一个字段或方法,而不指明它属于哪个类或接口类型.

    CONSTANT_NameAndType_info {
        u1 tag;
        u2 name_index;
        u2 descriptor_index;
    }
    

    name_index: 是一个索引值,指向常量池中一个CONSTANT_Utf8 类型的常量,

    descriptor_index:一个索引值,指向该字段或方法的描述符 所在 CONSTANT_Utf8 常量。

  • CONSTANT_Fieldref_info, CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info 结构

    CONSTANT_Fieldref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_Methodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_InterfaceMethodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    

    class_index: 是一个索引,指向一个 CONSTANT_Class_info常量。

    CONSTANT_Methodref_infoclass_index 指向的CONSTANT_Class_info 常量必定是一个 类而不是一个接口。

    CONSTANT_InterfaceMethodref_infoclass_index指向的必定是一个接口,而不是一个类。

    CONSTANT_Fieldref_infoclass_index指向的可能是一个类或者一个接口。

    name_and_type_index: 索引,指向一个 CONSTANT_NameAndType_info 常量,该常量存储了 字段或方法的名称和描述符

  • CONSTANT_MethodHandle_info(JDK1.7新增)

    该结构用于表示一个方法句柄

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

    reference_kind:取值范围为1 ~ 9。该值表示此方法句柄的类型,用于表征其字节码行为.

    KindDescriptionInterpretation
    1REF_getFieldgetfield C.f:T
    2REF_getStaticgetstatic C.f:T
    3REF_putFieldputfield C.f:T
    4REF_putStaticputstatic C.f:T
    5REF_invokeVirtualinvokevirtual C.m:(A*)T
    6REF_invokeStaticinvokestatic C.m:(A*)T
    7REF_invokeSpecialinvokespecial C.m:(A*)T
    8REF_newInvokeSpecialnew C; dup; invokespecial C.:(A*)V
    9REF_invokeInterfaceinvokeinterface C.m:(A*)T

    reference_index:reference_index项的值必须是constant_pool表的有效索引。索引处的constant_pool条目必须如下所示:

    • 如果reference_kind项的值是1 (REF_getField), 2 (REF_getStatic), 3 (REF_putField)、4 (REF_putStatic),constant_pool条目索引必须是CONSTANT_Fieldref_info结构,代表一个字段的处理方法被创建。(就是对字段的处理)

    • 如果reference_kind项的值是5 (REF_invokeVirtual)、8 (REF_newInvokeSpecial),constant_pool条目索引必须是CONSTANT_Methodref_info结构。代表一个类的方法或者构造函数要创建的方法处理。

    • 如果reference_kind项的值是6 (REF_invokeStatic)或7 (REF_invokeSpecial),

      若类文件版本号小于52.0(即JDK1.8以下版本),constant_pool条目是CONSTANT_Methodref_info结构,代表一个类的方法的方法创建句柄;

      如果类文件的版本号是52.0或更高(即JDK1.8版本及以上),索引处的constant_pool条目则是CONSTANT_Methodref_info结构体或CONSTANT_InterfaceMethodref_info结构体,代表一个类或接口的方法,该方法句柄将被创建。

      (**因为在JDK1.8中,接口可以定义 static 方法以及default方法,static修饰方法,通过接口类调用,而 default 方法通过接口方法调用,default修饰的方法可以有默认实现 **)

    • 如果reference_kind项的值是9 (REF_invokeInterface),那么索引处的constant_pool项必须是CONSTANT_InterfaceMethodref_info结构,该结构表示要为其创建方法句柄的接口方法。

  • CONSTANT_MethodType_info(JDK1.7新增)

    CONSTANT_MethodType_info结构用于表示一个方法类型

    CONSTANT_MethodType_info {
        u1 tag;
        u2 descriptor_index;
    }
    

    descriptor_index: 指向一个CONSTANT_Utf8_info 常量,代表一个方法类型描述

  • CONSTANT_InvokeDynamic_info(JDK1.7新增)

    CONSTANT_InvokeDynamic_info结构被invokedynamic指令(§invokedynamic)用来指定引导方法、动态调用名称、参数和调用的返回类型,以及可选的,引导方法的静态参数的附加常量序列。

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

    bootstrap_method_attr_index:其值必须是这个类文件的引导方法表的bootstrap_methods数组的有效索引。

    name_and_type_index:name_and_type_index项的值必须是constant_pool表的有效索引。索引处的constant_pool条目必须是CONSTANT_NameAndType_info结构,代表方法名和方法描述符.

1.3 access_flags(访问标志)

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

标志名称标志值含 义
ACC_ PUBLIC0x0001是否为public类
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语意,invokespecial 指令的语意在JDK 1.0.2发生过改变.为可区别这条指令使用哪种 语意.JDK 1.0.2之后编详出来的类的这个标志都必须为真
ACC_INTERFACE0x0200标识这是个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或并抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是个注解
ACC_ENUM0x4000标识这是一个枚举

access_falgs 占用了两个字节,16个比特,即其有16个标志位,目前共使用了8个,其他的标志位一律为0。

public final class Test{
    
}

如上Test类是一个 public类且被声明为 final,其 ACC_PUBLICACC_SUPERACC_FINAL标志位为真,其他标志位为假。 access_flags 值 0X0001|0x0010|0x0020=0x0031.

1.4 this_class(类索引)、super_class(父类索引)、interfaces(接口索引表)

​ 类索引、父类索引、接口索引表,class文件通过这三项描述类的继承关系。

​ 因为Java中所有类继承自Object,因此除了Object所有类的父类索引不为0,而Object父类索引为0.而接口interfaces_count 根据情况,值可能为0。后面接口的索引表不占用任何值。

1.5 field_info(字段表集合)

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

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • access_flag: 同 Class 的 access_flags作用类似。
Flag NameValueInterpretation
ACC_PUBLIC0x0001public 关键字修饰
ACC_PRIVATE0x0002private 关键字修改时
ACC_PROTECTED0x0004protect 关键字修饰
ACC_STATIC0x0008static 关键字修饰
ACC_FINAL0x0010final 关键字修饰
ACC_VOLATILE0x0040volatile 关键字修饰
ACC_TRANSIENT0x0080transient 关键字修饰
ACC_SYNTHETIC0x1000字段是否由编译器自动生成
ACC_ENUM0x4000enum 关键字修饰,枚举类
  • name_index: 指向一个CONSTANT_Utf8_info 常量,其是一个简单名称,如字段 int a,对应 的是a

  • descriptor_index:指向一个CONSTANT_Utf8_info 常量,其是字段类型的描述符,如定义一个字段int a;对应描述符是I

全限定名: 包括一个类所在包的信息。类似 /java/utils/List 这种,对应类的简单名称为List.

简单名称: 没有其他前缀

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

标识字符含义标识字符含义
B基本类型 byteJ基本类型 Long
C基本类型 charS基本类型 short
D基本类型 doubleZ基本类型 boolean
F基本类型 floatL对象类型,如 Ljava/lang/Object;
I基本类型 int[表示一维数组,每一维度则在类型前加上一个前置的**[**来表示,如 int[][Iint[][][[I

注意

  1. 还有一个标识符 V, 其只在方法描述中出现,其就是返回类型中的 void.
  2. 对象类型标识是有一个 ;号的,如果没有;,其显然无法判断一个对象类型到哪个字符才是终止,如Ljava/lang/Object后面跟个I,Ljava/lang/ObjectI 是表示一个ObjectI 类还是 表示两个类型 Object和int呢?。

下一段落将讲到方法描述符,所以,提前将方法描述符结构介绍一下。以下是从 oracle官网copy过来的方法描述符结构介绍。

MethodDescriptor:
	( {ParameterDescriptor} ) ReturnDescriptor
ParameterDescriptor
	FieldType
ReturnDescriptor:
	FieldType
	VoidDescriptor
VoidDescriptor:// void返回值对应的描述符`V`,在前面描述符表格下介绍了
	V

方法描述符的结构是 参数类型+返回类型 组合而成.

对于void m(int a,int c[][],String d),其方法描述符是(I[IILjava/lang/String;)V

对于String m(),其方法描述符是(Ljava/lang/String;)V

  • attributes_count、attributes[attributes_count]: 两个字节,attributes_count表示attributes个数,attributes 数组存储了字段的额外信息,一般情况下 attributes_count =0。当为 final 修饰时,会有一个名为ConstantValue的属性。

1.6 method_info(方法表集合)

方法表的结构与字段表基本相同,如下。

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

因为 方法不能被volailetransient 关键字修饰,所以去掉了ACC_VOLATILEACC_TRANSIENT。而synchronizednativestrictfpabstract 可以修饰方法。

Flag NameValueInterpretation
ACC_PUBLIC0x0001public 关键字修饰
ACC_PRIVATE0x0002private 关键字修改时
ACC_PROTECTED0x0004protect 关键字修饰
ACC_STATIC0x0008static 关键字修饰
ACC_FINAL0x0010final 关键字修饰,方法不可被重写
ACC_SYNTHETIC0x1000字段是否由编译器自动生成
ACC_ENUM0x4000enum 关键字修饰,枚举类
ACC_SYNCHRONIZED0x0020synchronized修饰
ACC_NATIVE0x0010native 修饰,调用so库中方法
ACC_STRICTFP0x0800strictfp 修饰,其修饰的方法范围内依照浮点规范IEEE-754 执行浮点数的运算
ACC_ABSTRACT0x0400abstract 修饰,表示一个抽象方法

注意:sstrictfp 是strict float point 的缩写指的是精确浮点,它是用来确保浮点数运算的准确性。 在Java虚拟机进行浮点运算时,如果没有指定strictfp关键字时,Java的编译器以及运行环境在对浮点运算的表达式是采取一种近似于我行我素的行为来完成这些操作,以致于得到的结果往往无法令你满意。而一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754(是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用)来执行。因此如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,那就请用关键字strictfp。

你可以将一个类、接口以及方法声明为strictfp,但是不允许对接口中的方法以及构造函数声明strictfp关键字

  • name_index:指向方法的名称

  • descriptor_index:指向方法的描述符,其在1.5 字段表的最后介绍了。

  • attributes: 方法的属性表,存储了额外信息。其中 名为 Code的 属性存储了方法的方法内的字节码指令!

1.7 attributes(属性表集合)

​ 属性表(attributeinfo)在前面的讲解之中已经出现过数次,在Class文件、字段表、方 法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。

​ 与CIass文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍 微宽松了一些,不再要求各个属性表具有严格顺序,并且只要不与已有属性名重复任何人 实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不 认识的属性。为了能正确解析CIass文件,《Java虚拟机规范(第2版)》中预定义了9项虚拟 机实现应当能识别的属性,而在最新的《Java虚拟机规范(JavaSE7)》版中,预定义属性 已经增加到21项,具体内容见表6.13。下文中将对其中一些属性中的关键常用的部分进行讲 解。

以上内容来自 《深入理解Java虚拟机第二版》

属性表类型如下:

AttributeLocation含义
SourceFileClassFile用于记录生成Class文件的源码文件名称,一个可选属性(定长属性),可以通过Javac 的 -g:none或 -g:source选项关闭或生成该项信息。对于大多数类来说,类名和文件名是一致的,但是有一些特殊情况(如内部类、文件内的非public类)例外,如果没有该项信息,在报错的时候,堆栈中将不会显示出错误代码所属的文件名。
InnerClassesClassFile内部类列表,记录当前类的所有内部类,包括方法中定义的类。如果当前类是一个内部类,则还要记录当前类的外部类,直到外部类不是一个内部类。
EnclosingMethodClassFile仅当一个类为局部类或匿名类时,才能拥有这个方法属性,用于表示这个类所在的外围方法。
SourceDebugExtensionClassFileJdk5 新增,用于存储额外的调试信息,比如在JSP文件进行调试时,无法通过对战来定位到JSP文件的行号,在JSR45题案中为这些非Java语言编写,却需要编译成字节码并运行在jvm中的程序提供一个进行调试的标准机制,使用该属性可以存储这个标准所加入调试信息。
BootstrapMethodsClassFilejdk7 新增的一个复杂的变长属性,用于保存 invokedyamic 指令引用的引导方法限定符。其真正的使用是到Java8中。
ConstantValuefield_infofinal 关键字定义的常量值(field_info结构中最多只有一个ConstantValue属性),且属性值只限于 基本数据类型(int,char等,注意:不包括 Integer 等封装类) 和String.在后面将更详细的介绍。其属性值只是一个指向 常量池的字符串,因此很好理解为什么只支持基本类型和String.
Codemethod_infoJava代码编译成的字节码指令
Exceptionsmethod_info列举可能跑出的受检查异常(Checked Exception), 即 方法 throws 关键字后面跟着的异常。
MethodParametersmethod_infoJDK8加入的一个变长属性,用于记录方法的各个形参的名称和信息。在Java7及之前,Class文件默认不存在方法参数名称(为了存储空间考虑),因为对于程序执行而言没有太大影响,但是这会影响Jar包传播,没有JavaDoc的Jar包,IDE编辑器无法提供智能提示。
SyntheticClassFile, field_info, method_info(标志类型的布尔属性,只存在有和没有的区别。)表示此字段或方法不是由Java源码直接生成的,而是由编译器自行添加的(也可以通过设置ACC_SYNTHETIC表示)。该方式通过生成源码中不存在的Synthetic 方法、字段甚至整个类的方式,实现了越权访问(比如访问private字段)。所有不通过用户代码生成的方法、字段、类都应该设置synthetic属性或ACC_SYNTHETIC 标志。(除了实例构造器<init>()和类构造器cinit())
DeprecatedClassFile, field_info, method_info(标志类型的布尔属性,只存在有和没有的区别。)被声明为deprecated 的类、字段或方法
SignatureClassFile, field_info, method_infoJDK5增加,可选定长属性。用于记录泛型签名信息。因为Java的泛型采用的是擦除法实现的伪泛型,其所有泛型信息将会在编译后被擦除,如此运行时做反射时无法获得泛型信息。而增加了Signature 属性,Java的反射API就能够通过该属性获取泛型类型。
LineNumberTableCode用于描述Java源代码行号与字节码行号。其不是运行时必须属性,但是如果取消该项信息,运行时报错,堆栈中将不会显示出错的行号,且在调试时无法按照源码行来设置断点。可通过-g:gone 取消,-g:lines 生成该信息。
LocalVariableTableCode用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,非运行时必须属性。可通过-g:gone 取消,-g:vars 生成该信息。没有该项,最大的影响就是其他人引用这个方法时**,所有的参数名称都将消失,会对代码编写带来不便,且调试期间无法根据参数名称从上下文中获得参数值。**
LocalVariableTypeTableCodeJDK 1.5 新增,使用特征签名代替描述符,是为了引入泛型越发之后能描述泛型参数化类型而添加,用于获取在方法运行时泛型局部变量的信息。
StackMapTableCodeJDK1.6 新增属性,供新的类型检查验证其(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配。(目前还不太清除这个属性作用)
AnnotationDefaultmethod_infoJDK5新增。用于记录注解类元素的默认值。
RuntimeVisibleAnnotations,ClassFile, field_info, method_infoJdk1.5 新增属性。 RuntimeVisibleAnnotations 为动态注解提供支持,用于指明那些注解是运行时(实际上运行时就是进行反射调用)可见的。
RuntimeInvisibleAnnotationsClassFile, field_info, method_infoJDK1.5新增属性。与 RuntimeVisibleAnnotations相反,用于指明那些注解是运行时不可见的
RuntimeVisibleParameterAnnotationsmethod_infoJDK 1.5新增属性,作用与RuntimeVisibleAnnotations 类似,只是作用对象为方法参数
RuntimeInvisibleParameterAnnotationsmethod_infoJDK 1.5新增属性,作用对象为方法参数,与RuntimeInvisibleParameterAnnotations相反,用于指明那些注解运行时是不可见的,即 不可通过反射调用。
RuntimeVisibleTypeAnnotations,ClassFile, field_info, method_info, CodeJDK1.8新增属性,为实现JSR 308中新增的类型注解提供的支持,用于指明那些类注解运行时类型可见注解。(暂时还未搞清楚其和RuntimeVisibleAnnotations区别)
RuntimeInvisibleTypeAnnotationsClassFile, field_info, method_info, CodeJDK1.8新增属性,运行时不可见类型注解。

局部类:代码块内定义的类,如下所示,LocalClass1 类的范围是function方法内,而LocalClass2类的范围是if 代码块内

public class Outer{

pulic void function(){

  class LocalClass1{//局部类

  }

  if(true){
			class LocalClass2{//局部类

  	}
  }
}

}

ConstantValue:注意,如下所示,s1、s2 、i1 、i4 字段具有 ConstantValue 属性。有兴趣的同学可以对其class文件,进行javap -v 查看下,那些有ConstantValue。

class Constant{
 public static final int SING_1 = 1;
}
class Test{
 public final String s1 = "1";
 public static final String s2 = "2";
 public final String s3 = new String("3");
 public final String s4;
 public final int i1 = 1;
	public final Integer i2 = 2;
	public final int i3 = new Integer(3);
 public final int i4 = Constant.SING_1;
 {
     s4 = "4";
 }
}

AnnitionDefault: 用于记录注解类元素的默认值,其修饰的方法的默认值。如下代码对应的class文件结构。

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@interface Color {  
    String color() default "red";  
}
  Last modified 2021-8-1; size 435 bytes
  MD5 checksum ed35b25f697b62b2640fe4067ae482d0
  Compiled from "Color.java"
interface Color extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
   #1 = Class              #2             // Color
   #2 = Utf8               Color
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             // java/lang/annotation/Annotation
   #6 = Utf8               java/lang/annotation/Annotation
   #7 = Utf8               color
   #8 = Utf8               ()Ljava/lang/String;
   #9 = Utf8               AnnotationDefault
  #10 = Utf8               red
  #11 = Utf8               SourceFile
  #12 = Utf8               Color.java
  #13 = Utf8               RuntimeVisibleAnnotations
  #14 = Utf8               Ljava/lang/annotation/Target;
  #15 = Utf8               value
  #16 = Utf8               Ljava/lang/annotation/ElementType;
  #17 = Utf8               TYPE
  #18 = Utf8               Ljava/lang/annotation/Retention;
  #19 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #20 = Utf8               RUNTIME
{
  public abstract java.lang.String color();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#10}
SourceFile: "Color.java"
RuntimeVisibleAnnotations:
  0: #14(#15=[e#16.#17])
  1: #18(#15=e#19.#20)

扩展知识

  1. String 最大长度 655354,编译器限制

  2. Java匿名 内部类中传入了外部类的对象,而kotlin进行了优化,在匿名内部类未引用外部类对象时,不传入外部类对象。

  3. 类的每个非静态方法其实都传入了一个参数this,在参数表的0位置。

  4. 对于匿名内部类,java如何将方法的对象传入的?其是在匿名内部类增加了隐藏的构造方法,传入了外部的变量