大多数人都看不到最后,因为有点长,希望你超越绝大多数人~赢在每一次。


 1 

前言概述

 本文旨在讲解class文件的整体结构信息,阅读本文后应该可以完整的了解class文件的格式以及各个部分的逻辑组成含义。class文件包含了java虚拟机指令集和符号表以及若干其他辅助信息。 class文件是一组以8位字节为基础单位的二进制字节流。各个数据项按照顺序紧凑的排列在Class文件中,中间没有任何分隔符号 ,class文件采用类似 c结构体的格式存储数据。数据类型只有两种:无符号数 和类c结构体的表,  表是由无符号数或者其他的表构成的。 整个class文件就是一张表。无论无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器用于指示接下来的数据个数,然后是若干个连续的数据项 。 class文件主要内容为:  类本身的信息、字段、方法、常量池以及方法中的Code属性,再就是一些相关的辅助信息 。类本身的信息类本身有一些必备的描述信息,比如类名、访问修饰符、继承关系等;字段用于描述接口或者类中声明的变量;字段包括类变量以及实例变量,不包括局部变量,他有访问标志 名称 描述符信息;方法用于描述方法表信息  类似字段 也有访问标志 名称 描述符信息;常量池可以理解为Class文件的资源仓库,所以他是与其他项目关联最多的数据类型;
主要是两大类:  字面量 以及符号引用 。字面量接近java语言层面的常量概念,比如文本字符串,声明为final常量的值。符号引用包括:
  • 类和接口的全限定名

  • 字段的名称和描述符

  • 方法的名称和描述符


虚拟机加载class文件的时候动态链接,所以class文件中不会保存方法的最终内存布局,还需要转换。虚拟机运行时从常量池中获得对应的符号引用,然后在创建或者运行时解析翻译到具体的内存地址中。Code属性存放的Java方法的内容,位于方法method_info  内。存放的是java方法经过编译器编译成的字节码指令, 说白了存放的就是代码的编译后形式。 概述:class文件作为JVM的"机器语言" 主要包括两部分的信息,基础信息以及附加信息。基础信息为源代码中呈现的信息:类自身信息/字段/方法用于描述源代码中的类/字段/方法;常量池中保存了资源信息,比如字段的名字,方法的描述符等;方法中的code属性保存了方法中的代码的执行逻辑;
额外信息为虚拟机执行过程中或者字节码指令执行所需要的信息:主要是为了保证虚拟机能够正确的加载class文件;另外虚拟机加载类还需要做一些额外的工作比如校验信息等;字节码指令的执行可能还需要一些额外的信息;这些额外的信息通常也是保存在常量池中或者以属性的形式出现; 所以想要理解class文件的内容,就需要从这两个方面出发:
  • 基础信息

  • 额外附加信息

基础信息是对于源代码的映射,给出任意一段java代码,源代码中到底有哪些信息呢?比如类的全限定名、类的属性信息、访问权限等;字段的名称、类型信息、访问权限等;方法的名称、方法签名、返回值类型、访问权限等;类或者方法或者字段有没有注解?类的父类是什么?类继承了哪些接口? 等等等等其实换句话说你所有使用依赖的功能,都需要有底层的数据来支撑。 额外的附加信息主要涉及到字节码指令以及虚拟机加载相关原理,额外的附加信息是附属于基本信息的,他们渗透在基本信息里面。所以下面的说明中,我们以基础信息为纲要,涉及到的附加信息会说明或者从功能上看出来是作为附加信息。 2 

class文件的数据格式了解

class文件包含了虚拟机所需要知道的,关于类或者接口的所有信息。 结构体他的基本数据类型为无符号数,以及表,表是数据组织结构类似于C语言中的结构体的的一种形式。为了更好地理解这种形式的逻辑,不了解C语言的,可以稍微了解一点结构体的形式,更有利于理解class文件的数据形式。
struct 结构体名{    类型名1 成员名1;    类型名2 成员名2;                        .....    类型名n 成员名n};
比如
struct student{char name[10];char sex;int age;float score;};

他的每个元素的数据类型都可以不相同 ,而且每个字段本身也可以是指向另外一个数据项的地址 。也类似与数据库中的关联字段ID,这个ID在另一个表中有代表一整条的记录。比如学生表有addressId字段,用于关联地址信息;地址是一条完整的记录,其中可能包括 国家地区 省市 乡镇等等字段值;  3 

class文件中的数据类型

每一个class文件都是由字节流组成,一个字节8位。所有的16位 32位 和 64位数据长度都可以通过构造成2个 4个或者8个字节来表示。多字节的数据将会大端排序(高位字节在地址最低位 )
JVM|class文件浅析(二)_java 对于字节的描述,java虚拟机规范中使用u1  u2  u4  u8来分别表示1,2,4和8 个字节的无符号数。基本数据类型为u1,u2,u4,u8 。复杂的数据类型由类似结构体的形式来组织无符号数或者是类结构体的形式,可以称之为表,也就是说表里面可以有表。比如常量池表中的数据结构为
cp_info{u1 tag;u1 info[ ];}
所以说
class文件的形式是一张巨大的表,是一个二进制字节流。只有两种数据表示形式:无符号数以及表(结构体 复合的数据结构);各个数据项严格的按照顺序存放,之间没有填充或者对齐,这也是为何编译后代码如此紧凑的原因之一。基本数据类型为: u1  u2   u4  u8。 4 

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文件是一张表这张表里面记录了class文件本身的相关信息,比如magic 以及版本号。class文件中类索引 this_class、父类索引 super_class 以及接口集合 interfaces 三个数据项确定了类的继承关系。 同时又记录了字段 方法 以及属性信息。 还是以之前的例子为例,源代码
public class HelloWorld {private int x;private String y;public void fun() {}public static void main(String[] args) {    System.out.println("hello world");  }}
使用Javap解析后的数据使用WinHex打开.class 文件 的部分结果我们对照着class文件的结构进行解析:

注意:

上图中一列是一个字节也就是8位 ,表示两个十六进制数,也就是相当于u1 ,  CAFEBABE 占据4列,也就是u4。



第一项 u4            magic 符合class文件魔数设置0xCAFEBABE

第二项  u2            minor_version


第三项  u2            major_version

所以说 主版本号为 52(34是十六进制) 次版本号为0,与javap解析后的数据吻合

第四项 u2            constant_pool_count

十六机制27  十进制39
可以看到javap解析后的Constant pool:中总共有从#1 到 #38  
常量池计数器constant_pool_count的值等于常量表中的成员数加1
常量池标的索引值只有大于0 且小于constant_pool_count时才有效
所以此处解析也是对的

第五项 cp_info       constant_pool[constant_pool_count-1]
他是常量池,常量池表中的所有项目的格式为
cp_info{u1 tag;u1 info[];}
此处只是一个格式,表示有一个tag u1,还有不定个数的u1,具体形式由tag的值决定。

因为常量池计数为39 ,常量池中个数为[constant_pool_count-1]所以是38,也就是接下来会有38个-{u1 和 多个u1 } 这种形式的数据组合。
第一个tag

tag = 7 表示 CONSTNT_Class
结构为
CONSTANT_Class_info{u1 tag;u2 name_index;}
所以tag 之后,接下来有一个u2 表示name_index

name_index 的值是常量池中的一个索引,他指向的是2号索引,也就是

在接下来的一个u1,是下一个常量池数据项的tag

tag 为1 表示CONSTANT_Utf8
他的结构为
CONSTANT_Utf8_info{u1 tag;u2 length;u1 bytes[length];}
所以接下来的两个为length 

表示长度为10。
在接下来是数组的起始地址,长度为10,那么就是10个u1

我们翻一下ASCII码表,对照下十六进制与字符
48        H
65        e
6C        l
6C        l
6F        o
57        W
6F        o
72        r
6C        l
64        d  
也就是HelloWorld  这其实就是我们的类名
不在继续一一比对,你可以使用javap 进行查看,这是官方提供的class文件的解析工具。 小结:class文件的存储形式就是一个二进制字节流,使用类似结构体的形式,将源代码映射的基础信息以及运行时必要的辅助信息。而这些基础信息也都已经被分割为最小的数据单位,进行合理紧凑的组织起来。 JVM|class文件浅析(二)_java_02  5 

classFile文件格式

ClassFile {u4 magic;//唯一作用是确定这个文件是否为一个能被虚拟机所接受的class文件。魔数值固定为0xCAFEBABE,不会改变u2 minor_version;//唯一作用是确定这个文件是否为一个能被虚拟机所接受的class文件。魔数值固定为0xCAFEBABE,不会改变u2 major_version;//主版本号u2 constant_pool_count;//常量池计数  值等于常量池表中的成员个数加1cp_info constant_pool[constant_pool_count-1];//常量池 1~ constant_pool_count-1 为索引u2 access_flags;//访问标志以及类型信息u2 this_class;//当前类索引 指向常量池中一个CONSTANT_Class_infou2 super_class;//父类索引 0 或者指向常量池中一个CONSTANT_Class_infou2 interfaces_count;//直接超接口数量u2 interfaces[interfaces_count];//接口表u2 fields_count;//字段个数 static类变量或者非sttic的实例变量 不包括继承的field_info fields[fields_count];//字段表u2 methods_count;//方法个数 所有方法 但不包括继承而来的method_info methods[methods_count];//方法表u2 attributes_count;//属性个数attribute_info attributes[attributes_count];/属性表}
从class文件的数据结构上来看,主要有下面几部分信息内容:class文件本身的信息:
  • magic

  • minor_version  

  • minor_version


类本身的信息:
  • access_flags   

  • this_class  

  • super_class     

  • interfaces_count    

  • interfaces[interfaces_count]


常量信息:
  • constant_pool_count     

  • constant_pool[constant_pool_count-1]


字段:
  • fields_count     

  • fields[fields_count]


方法:
  • methods_count      

  • methods[methods_count]


属性:
  • attributes_count     

  • attributes[attributes_count]


  6 

各种名称的内部表示形式

在进入更加详细的说明之前,有一部分内容必须提前说一下,那就是一些内部名称数据的表示形式。就好像编码一样,亦或者理解成书写格式与规范,比如我们会把姓写在名的前面。对于class文件中描述的一些信息,我们有固定的格式的信息描述符号,比如下面会提到的我们用  D表示是double类型。 主要涉及两部分内容 :名称和描述符


名称描述

类和接口的二进制名称class文件中的类和接口的二进制名称,是通过全限定名称来进行表示的,称之为二进制名称。注意全限定名的分割形式不再是 英文句号 .  而是  /   比如 java/lang/System。
非限定名方法名 字段名  局部变量名以及形式参数的名都采用非限定名的形式。 

描述符

分为字段描述符、方法描述符字段描述符上面截图自The Java Virtual Machine Specification, Java SE 8 Edition  他表示字段描述符使用FieldType来进行表述。FieldType有包括基本类型/对象类型/数组类型

 比如int 为I;Object 为 L java/lang/Object; double[][][] d 为 [[[D; 方法描述符上面截图自The Java Virtual Machine Specification, Java SE 8 Edition   他表示一个方法描述符由一个参数描述符ParameterDescriptor  和一个返回类型描述符ReturnDescriptor组成。参数类型描述符:   FieldType 类型;
返回类型描述符:   FieldType类型或者Void类型VoidDescriptor;
Void类型描述符:使用的是V来进行表示,形式是( {ParameterDescriptor} ) ReturnDescriptor

注意:   

{}  不是一部分,是想表达和数组似的,也可能是多个

比如 Object m(int i, double d, Thread t) {...}他的描述符为:(IDLjava/lang/Thread;)Ljava/lang/Object;   7 

class文件详解之类本身信息

类本身的信息   access_flags   
  • this_class

  • super_class

  • interfaces_count

  • interfaces[interfaces_count]       

access_flag 字段为类的访问权限标志以及类型值。
this_classsuper_classinterfaces[interfaces_count] 构成了类的继承关系,指向的都是常量池中的CONSTANT_Class_info。对于super_class来说,可能为0。因为Object没有父类,其他所有都需要有对应的值存在。 8 

class文件详解之常量池

主要分为两类:
  • 字面量

  • 符号引用

字面量类似java语言层面的含义,文本字符串声明为final 的常量值。符号引用包括:
  • 类和接口的全限定名

  • 字段的名称和描述符

  • 方法的名称和描述符


常量池包含了class文件结构及其子结构中引用的所有的:字符串常量、类或者接口名、字段名、以及其他常量 。class文件由无符号数和表结构两种类型组成 关于名称的书写规范格式上面已经进行了描述,但是怎么表述这些名称的"字符串" 形式呢? 比如className = "Helloworld" 怎么保存Helloworld?另外一些基本的数据类型的数据在class文件中又将是如何存放呢?比如 int类型的x=5,这个5又怎么保存?字符串以及不同数值类型也都不就是一个u1,所以也需要组织形式也就是数据结构, 也就是表。 

常量池中的表结构的类型可以分为三种类型

1.)基本数据类型。

比如 int long的描述形式,虽然class文件是二进制字节流,最小为u1  但是这些基本数据类型在逻辑意义上来说,才是最小的描述单位。

2.)用于表述, 用于描述各个部分包含的逻辑内容的表,也就是类"结构体" 复合形式的数据类型结构。

3.)中间的映射结构表 相当于数据库中的中间关系表。


另外所有的常量池中的数据都是cp_info形式,所有的常量池中的数据都完全遵循这个格式。

cp_info{u1 tag;u1 info[];}

只不过info[]具体的格式,由tag来进行决定。也就是说不同的tag,那一块区域的大小以及表示的含义自然是不同的。其实tag 跟我们平时写代码时候的类型是一个道理,就是接下来这块区域的内容的类型标记。tag值如下:也就是总共有下面这些类型的常量池信息

CONSTANT_Class7                        
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_String8
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_NameAndType12
CONSTANT_Utf81
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18

 

常量池中的基础数据类型部分

我们先说下常量池中的封装好的数据类型部分字符串常量

CONSTANT_Utf8_info {    u1 tag;    u2 length;    u1 bytes[length];}
tag是CONSTANT_Utf8 ,值为1 。
字符串采用改进过的UTF-8编码表示;
接下来是编码后的字符串占用字节数以及字符串;
class文件中的方法字段名称都是此类型
int整型 4字节
CONSTANT_Integer_info {    u1 tag;    u4 bytes;}
tag为CONSTANT_Integer  值为3,大端排序的int值。
单精度浮点型 float 4字节
CONSTANT_Float_info {    u1 tag;    u4 bytes;}
tag为CONSTANT_Float  值为4,大端排序  IEEE754单精度格式   的floa值。
long与double 是64位 8个字节,分为4个高字节和4个低字节。
long 长整型 8字节
CONSTANT_Long_info {    u1 tag;    u4 high_bytes;    u4 low_bytes;}
tag为CONSTANT_Long 值为5,大端排序的long值。
双精度浮点型 double 8字节
CONSTANT_Double_info {    u1 tag;    u4 high_bytes;    u4 low_bytes;}
tag为CONSTANT_Double 值为6,大端排序的 double值。
除了基本数据类型其他的就是复合数据类型。所有的复合数据类型基本上都会包含其他的数据结构 ,这种包含方式使用的就是索引 #xxx的形式,指向常量池中的另外一项数据。

常量池中的中间关系映射类型部分

CONSTANT_NameAndType_info {    u1 tag;    u2 name_index;    u2 descriptor_index;}
tag为 CONSTANT_NameAndType 值为12,NameAndType  就是名称和类型的意思。
对于方法、字段来说:
他们都有变量名称或者方法名称;
他们也都有变量类型和方法签名(方法的类型);
NameAndType 是作为一个中间表形式的数据结构,字段、方法中都有一个索引指向他,他又指向了实际的名称和类型。
不管是方法名称还是字段名称,不管是方法签名还是字段类型都是字符常量的形式,name_index 和 descriptor_index 指向的都是CONSTANT_Utf8_info。

常量池中的复合数据类型部分

String类型的常量对象

CONSTANT_String_info {    u1 tag;    u2 string_index;}
tag为CONSTANT_String,值为8,他表示的是String类型的数据。我们知道String是常量,字符串常量是用CONSTANT_Utf8_info进行表示的,所以 String_index指向的就是对应的CONSTANT_Utf8_info的"行号"。
方法类型
CONSTANT_MethodType_info {    u1 tag;    u2 descriptor_index;}
CONSTANT_MethodType ,值为16 
CONSTANT_NameAndType_info 是一个用于字段或者方法结构中的中间结构,包含了名称和类型 。
CONSTANT_MethodType_info  仅仅表示的就是方法签名,方法签名对应的是CONSTANT_Utf8_info,所以descriptor_index  指向 方法类型描述符的CONSTANT_Utf8_info。
类或接口
CONSTANT_Class_info {    u1 tag;    u2 name_index;}
tag 为CONSTANT_Class,值为7,名称自然是字符串常量也就是CONSTANT_Utf8_info,所以 name_index指向常量池中的 CONSTANT_Utf8_info。
字段
CONSTANT_Fieldref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}
CONSTANT_Fieldref,值为9。
class_index 表示当前字段对应或者说所属的-类或者接口,类和接口都可能 。
class_index指向CONSTANT_Class_info;
name_and_type_index 表示当前字段的名称和类型;
name_and_type_index指向CONSTANT_NameAndType_info;


方法
CONSTANT_Methodref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}
CONSTANT_Methodref ,值为10,class_index 表示当前方法 对应或者说所属的类,必须是类,不能是接口。
class_index指向CONSTANT_Class_info;
name_and_type_index 表示当前方法的名称和方法签名;name_and_type_index指向CONSTANT_NameAndType_info;
接口方法
CONSTANT_InterfaceMethodref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}
CONSTANT_InterfaceMethodref,值为 11。
class_index 表示当前方法 对应或者说所属的接口,必须是接口 不能是类。
class_index指向CONSTANT_Class_info;
name_and_type_index 表示当前方法的名称和方法签名;name_and_type_index指向CONSTANT_NameAndType_info;
方法调用
CONSTANT_MethodHandle_info {    u1 tag;    u1 reference_kind;    u2 reference_index;}
CONSTANT_MethodHandle ,值为 15。
方法调用,顾名思义也就是描述 方法的调用。
对于一个方法调用来说,方法可能有不同的类型,不同的类型有不同的操作对象。
reference_kind 正是描述方法的调用类型;
reference_index 描述的是方法的操作目标;
reference_kind 的值为1~9 他的类型决定了方法句柄的类型;
句柄类型的值表示方法句柄中字节码行为;
用于表示invokedynamic指令
CONSTANT_InvokeDynamic_info {    u1 tag;    u2 bootstrap_method_attr_index;    u2 name_and_type_index;}
tag为CONSTANT_InvokeDynamic,值为18。
CONSTANT_InvokeDynamic_info是为了字节码指令 invokedynamic  使用的。
invokedynamic是为了更好的支持动态类型语言,Java7通过JSR292给JVM增加的一条新的字节码指令。bootstrap_method_attr_index  的值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引;
name_and_type_index 指向CONSTANT_NameAndType 表示方法名和方法描述符; 9 

class文件详解之字段

字段表field_info 用于描述接口或者类中声明的变量,包括类变量 以及 实例变量  不包括方法内部声明的局部变量。可以包括的信息包括

  • 字段的作用域 public private protected

  • 字段类型 类变量还是实例变量  是否有static修饰

  • 是否为常量  final

  • 并发可见性 volatile

  • 是否可以被序列化  transient

  • 字段的数据类型  基本类型 对象 数组  

  • 字段名称

字段:

field_info {    u2             access_flags;    u2             name_index;    u2             descriptor_index;    u2             attributes_count;    attribute_info attributes[attributes_count];}
每个字段都由field_info结构定义。
同一个class文件中不会有两个字段同时具有相同的字段名和描述符。
access_flags 表示字段访问权限和基本属性;
name_index指向字段的名字 CONSTANT_utf8_info;
descriptor_index 指向字段描述符CONSTANT_utf8_info;
字段field  包含属性表,属性表结构的情况稍后介绍。
access_flags字段类型:


如同源代码中abstract不能和final同时使用,此处的标志位规则也是如此,有些标志是互斥的一个字段最多只能设置下面三者之一:

  • ACC_PUBLIC

  • ACC_PRIVAT

  • ACC_PROTECTED 

不能同时设置ACC_FINAL 和 ACC_VOLATILE ;接口中所有字段都具有 ACC_PUBLIC、ACC_STATIC、ACC_FINAL,也可以设置ACC_SYNTHETIC,其他的都不行了。 10 

class文件详解之方法

方法

method_info {    u2             access_flags;    u2             name_index;    u2             descriptor_index;    u2             attributes_count;    attribute_info attributes[attributes_count];}
所有方法,包括实例初始化方法以及类或者接口初始化方法。
一个class文件中不会有两个方法具有相同的方法名和描述符。
name_index 指向方法名字 CONSTANT_Utf8_info ;
descriptor_index 表示方法描述符 指向 CONSTANT_Utf8_info ;
方法也有属性表。access_flag 标志,基本也同方法的修饰符差不多

volatile关键字和transient关键字不能修饰方法,所以自然也没有这些个标志。而synchronized、native、strictfp 和 abstract 关键字可以修饰方法, 所以相对于字段中的标志新增了对应的标志。 类似字段,有些方法修饰符标志也是互斥的。一个方法只能设置下面三者之一:

  • ACC_PUBLIC

  • ACC_PRIVATE

  • ACC_PROTECTED

接口方法可以设置 除了下面几个以外的、以外的:

  • ACC_PROTECTED

  •  ACC_FINAL

  • ACC_SYNCHRONIZED

  • ACC_NATIVE


版本号小于52 每个方法必须设置 :

  • ACC_PUBLIC

  • ACC_ABSTRACT


大于等于52 每个方法必须设置ACC_PUBLIC  或者 ACC_PRIVATE 中的一个;ps: 52 可以理解为jdk1.8
如果设置了ACC_ABSTRACT,不能再设置下面的:

  • ACC_FINAL

  • ACC_NATIVE

  • ACC_PRIVATE

  • ACC_STATIC

  • ACC_STRICT

  • ACC_SYNCHRONIZED


实例初始化方法只能被下面中之一修饰

  • ACC_PUBLIC

  • ACC_PROTECTED

  • ACC_PRIVATE

还可以设置:

  • ACC_STRICT

  • ACC_VARARGS

  • ACC_SYNTHETIC  

其他的都不能再设置;
类或者接口的初始化方法,由虚拟机自动调用。除了ACC_STRICT以外,其它标志全部都会被忽略; 11 

class文件详解之属性

通过类、常量池、字段、方法的结构,已经塑造完成了 class文件的基本概念。他们是class文件的基础骨架。骨架之上还有其他很多的附属信息以及比如运行时需要的额外的信息。这些信息大多数不能归结于一大类,逻辑架构上可能比较散乱,也可以理解为杂项信息。这些杂项就都是属性表的范畴。不过Code属性比较特殊,他其实也算作是一个骨架部分,或者说一个重要"器官",他是作为方法中的代码编译后的字节码形式存在的。只不过因为逻辑上方法内的代码字节码指令,显然是归属于某个方法的,所以Code作为属性表也可以理解。

  • class文件的ClassFile结构

  • 字段的field_info 结构

  • 方法的method_info结构

  • Code属性

以上四类都包含属性结构信息,所有属性表的梗概结构为:

attribute_info {u2 attribute_name_index;u4 attribute_length;u1 info[attribute_length];}
attribute_name_index  表示属性的名字索引 指向 CONSTANT_Utf8_info;attribute_length就是属性的长度 ;info[attribute_length] 是属性的具体数据信息;

属性分类

所有的属性按照用途,可以划分为三类

1.)对于JVM 正确解读class文件起关键作用的5个属性
• ConstantValue• Code• StackMapTable• Exceptions• BootstrapMethods
2.)对JavaSE 平台类库正确解读class文件起关键作用的12个属性
• InnerClasses• EnclosingMethod• Synthetic• Signature• RuntimeVisibleAnnotations• RuntimeInvisibleAnnotations• RuntimeVisibleParameterAnnotations• RuntimeInvisibleParameterAnnotations• RuntimeVisibleTypeAnnotations• RuntimeInvisibleTypeAnnotations• AnnotationDefault• MethodParameters
3.)对JVM或者JavaSE平台类库能够正确解读class文件
虽然不起关键作用,但是却可以作为实用工具来使用的6个属性
• SourceFile• SourceDebugExtension• LineNumberTable• LocalVariableTable• LocalVariableTypeTable• Deprecated
我们已经知道 属性出现于 classFile、field_info、method_info、code 中,所以换一个角度分析。


所有属性按照位置划分

另一种位置划分

classFile

  • SourceFile

  • InnerClasses

  • EnclosingMethod

  • SourceDebugExtension

  • BootstrapMethods

  • Synthetic

  • Deprecated

  • Signature

  • RuntimeVisibleAnnotations,

  • RuntimeInvisibleAnnotations

  • RuntimeVisibleTypeAnnotations,

  • RuntimeInvisibleTypeAnnotations


field_info

  • ConstantValue

  • Synthetic

  • Deprecated

  • Signature

  • RuntimeVisibleAnnotations,

  • RuntimeInvisibleAnnotations

  • RuntimeVisibleTypeAnnotations,

  • RuntimeInvisibleTypeAnnotations


method_info

  • Code

  • Exceptions

  • RuntimeVisibleParameterAnnotations,

  • RuntimeInvisibleParameterAnnotations

  • AnnotationDefault

  • MethodParameters

  • Synthetic

  • Deprecated

  • Signature

  • RuntimeVisibleAnnotations,

  • RuntimeInvisibleAnnotations

  • RuntimeVisibleTypeAnnotations,

  • RuntimeInvisibleTypeAnnotations


Code

  • LineNumberTable

  • LocalVariableTable

  • LocalVariableTypeTable

  • StackMapTable


属性表中的attribute_name_index ,都是对应的属性名称,指向CONSTANT_Utf8_info。

比如Code属性值为Code,ConstantValue属性为ConstantValue。


ConstantValue 属性

通知虚拟机为静态变量赋值。只有被static关键字修饰的变量才可以使用这个属性,也就是只有类变量才可以使用。非static类型的变量,也就是实例变量的赋值,在构造方法中<init>。类变量可以再<clinit>方法中,也可以使用ConstantValue  属性。目前编译器的做法是 如果同时使用final和static来修饰,也就是常量了;如果变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue;如果没有final 或者并非基本类型或者字符串 选择在<clinit>中;

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

Code属性

Code属性是方法体中的代码经过编译处理后,最终的字节码指令。既然是方法体的内容,如果没有方法体自然没有code属性, 比如 接口或者抽象类中就不存在Code属性,native也不存在。

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];}
attribute_name_index 指向CONSTANT_Utf8_info 常量值, 固定为"Code" 表示属性的名称。attribute_length  属性值长度   属性表前面6个字节 u2 + u4    再加上attribute_length  就是整个表的长度了max_stack 操作数栈的最大深度 方法执行任意时刻不会超过这个值,根据值来分配栈帧  
ps:

虚拟机栈是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈。

虚拟机栈表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。

每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。

虚拟机栈的生命周期和线程是相同的

上面的代码除了正常执行以外,如果try出现Exception或者子类的异常,转到catch;如果出现不属于Exception或者子类的异常 转到finally;如果catch中出现任何异常,转到finally;

StackMapTable属性

用于虚拟机类型检查的验证阶段。是为了一种新的类型检查验证器而设置的,新的验证器在编译阶段将一系列的验证类型直接记录在class文件中,通过检查这些验证类型代替了类型推导过程。 Code属性表里最多可以包含一个StackMapTable,StackMapTable包含0个或者多个栈帧映射。用于表示执行到该字节码时局部变量表和操作数栈的验证类型。类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型。来确定一段字节码指令是否符合逻辑约束。 版本号大于等于50的class文件中,如果方法的code属性中没有附带StackMapTable属性,意味着他有一个  隐式的栈帧映射属性。隐式的栈映射属性的作用等同于number_of_entries 值为0的StackMapTable。
StackMapTable_attribute {    u2              attribute_name_index;    u4              attribute_length;    u2              number_of_entries;    stack_map_frame entries[number_of_entries];}
number_of_entries给出来的是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;}

Exceptions属性

不是Code中的exception_table   注意区分。列举出方法中可能抛出的已检查的异常,也就是方法声明throws后面的内容。
Exceptions_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 number_of_exceptions;    u2 exception_index_table[number_of_exceptions];}
attribute_length表示长度attribute_name_index 指向名字;
number_of_exceptions  表示个数;exception_index_table 指向常量池中的CONSTANT_Class_info 表示异常类型; 

BootstrapMethods 属性

保存invokedynamic 指令引用的引导方法限定符。如果某个classFile文件中常量池中至少有一个CONSTANT_InvokeDynamic_info,那么就必须包含且只能包含一个BootstrapMethods。与invokedynamic指令和java.lang.invoke包关系密切。
BootstrapMethods_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 num_bootstrap_methods;    {  u2 bootstrap_method_ref;        u2 num_bootstrap_arguments;        u2 bootstrap_arguments[num_bootstrap_arguments];    } bootstrap_methods[num_bootstrap_methods];}

InnerClasses  属性

记录内部类与宿主类之间的关联如果一个类内部定义了内部类,编译器就会为以及他所包含的内部类生成InnerClasses属性。
InnerClasses_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 number_of_classes;    {   u2 inner_class_info_index;        u2 outer_class_info_index;        u2 inner_name_index;        u2 inner_class_access_flags;    } classes[number_of_classes];}
inner_class_info_index、outer_class_info_index都是指向常量池中CONSTANT_Utf8_info ,分别代表内部类和宿主类的内部引用。inner_name_index指向常量池中CONSTANT_Utf8_info 表示内部类的名称,匿名内部类 为0。inner_class_access_flags   内部类的访问标志。


Synthetic  属性

标志是否有编译器自动生成 ,没有具体的值  只有存在和不存在的说法。
Synthetic_attribute {    u2 attribute_name_index;    u4 attribute_length;}
attribute_length  固定为0,也就是没有值attribute_name_index  指向CONSTANT_Utf8_info  表示 synthetic。
 

Signature 属性

可选的属性,1.5之后 任何类、接口、初始化方法 或者成员的泛型签名如果包含了类型变量或者参数化类型,那么signature 属性记录泛型签名信息。之所以需要是因为泛型擦除机制。反射机制获取泛型类型 依赖数据就是这个属性。
Signature_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 signature_index;}

EnclosingMethod 属性

位于classFile结果的属性 当且仅当class为局部类和匿名内部类时 才具有这个属性class_index  表示包含当前类的最内层类method_index表示当前类是否在某个方法或者构造器中,如果不是 值为0
EnclosingMethod_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 class_index;    u2 method_index;}

RuntimeVisibleAnnotations 

RuntimeInvisibleAnnotations 属性

添加在类声明、字段声明、方法声明上面的注解,在运行时的可见情况。Visible 可见  Invisible不可见。 ClassFile、field_info、method_info中最多只能有一个RuntimeVisibleAnnotations   或者  RuntimeInvisibleAnnotations;RuntimeVisibleAnnotations   和  RuntimeInvisibleAnnotations  基本一致,  但是 RuntimeInvisibleAnnotations 标志的不能被反射API访问;除非虚拟机通过与实现相关的特殊方式保留这些注解,否则,虚拟机将忽略Invisible的注解。num_annotations表示注解的数量;annotations  每个元素都表示一个注解; 
RuntimeVisibleAnnotations_attribute {    u2         attribute_name_index;    u4         attribute_length;    u2         num_annotations;    annotation annotations[num_annotations];}
RuntimeInvisibleAnnotations_attribute {    u2         attribute_name_index;    u4         attribute_length;    u2         num_annotations;    annotation annotations[num_annotations];}
annotation {    u2 type_index;    u2 num_element_value_pairs;    {  u2            element_name_index;       element_value value;    } element_value_pairs[num_element_value_pairs];}
 type_index 用来表示一个字段描述符,字段描述符表示一个注解类型,和当前annotation 结构所表示的注解一致。num_element_value_pairs  表示注解中的键值对 (注解中的参数都是键值对)  的个数element_value_pairs  代表真正的键值对,它包括:element_name_index表示键    element_value   表示值
element_value {    u1 tag;    union {        u2 const_value_index;        {   u2 type_name_index;            u2 const_name_index;        } enum_const_value;        u2 class_info_index;        annotation annotation_value;        {   u2            num_values;            element_value values[num_values];        } array_value;    } value;}

element_value 表示一个联合体。tag 使用u1 来表示键值对中的值是什么类型,也就是决定了键值对中  值的格式与value 中的哪一项相符合。联合体总共有五种:
  • const_value_index

  • enum_const_value

  • class_info_index 

  • annotation_value

  • array_value

tag  值表示的类型 

const_value_index   表示原始类型的常量值 或者String类型的字面量enum_const_value  表示一个枚举常量type_name_index  指向CONSTANT_Utf8_info 枚举常量类型的二进制名称的内部形式const_name_index 指向CONSTANT_Utf8_info 枚举常量的简单名称class_info_index  
表示类字面量  CONSTANT_Utf8_info,用于表示返回描述符,返回描述符给出了与该element_value结构所表示的类字面量相对应的类型。如果类字面量是C. class,且C是类、接口或数组类型的名字,那么对应的类型就是C。常量池中的返回描述符会是ObjectType  或者ArrayType。如果类字面量是p. class,且p是原始类型的名称,那么对应的类型就是p    常量池中的返回描述符会是一个BaseType。如果类字面量是void. class,那么对应的类型就是void。常量池中的返回描述符会是V.比如Object.class 对应于类型Object 所以常量池中就是Ljava/lang/Object;  而 int.class对应于类型int 常量池中就是I(大写的i )annotation_value    表示键值对中里面的值本身又是一个注解
array_value    表示键值对的  值   是一个数组num_values  给出了当前element_value结构所表示的数组的成员数量values   每个成员对应了当前element_value 结构所表示的数组中的一个元素

RuntimeVisibleTypeAnnotations    RuntimeInvisibleTypeAnnotations   属性

classFile、field_info、method_info  或者code属性中都有。比如对于某个类声明implements后面的类型所加的注解,记录在classFile结构体的这个属性里;比如某个字段声明中的类型 所加的全部注解记录在字段的对应属性里; 记录了标注在对应类声明、字段声明或者方法声明所使用的类型上面的注解,在运行时的可见情况,分为可见和不可见两种。也记录了,标注在对应方法体中,某个表达式所使用的类型上面的,运行时,可见注解。此外还记录了标注在泛型类、接口、方法以及构造器的,类型参数声明,上面的注解。Java虚拟机必须使这些注解可供取用。最多只能有一个num_annotations  表示注解个数annotations  表示每一个注解 类型为type_annotation
RuntimeVisibleTypeAnnotations_attribute {    u2              attribute_name_index;    u4              attribute_length;    u2              num_annotations;    type_annotation annotations[num_annotations];}
RuntimeInvisibleTypeAnnotations_attribute {    u2              attribute_name_index;    u4              attribute_length;    u2              num_annotations;    type_annotation annotations[num_annotations];}
type_annotation {    u1 target_type;    union {        type_parameter_target;        supertype_target;        type_parameter_bound_target;        empty_target;        method_formal_parameter_target;        throws_target;        localvar_target;        catch_target;        offset_target;        type_argument_target;    } target_info;    type_path target_path;    u2        type_index;    u2        num_element_value_pairs;    {         u2   element_name_index;      element_value value;    }     element_value_pairs[num_element_value_pairs];}
前三项 target_type target_info 以及 target_path  指出了带注解的类型所在的精确位置  target表示那个类型后面type_index   num_element_value_pairs       element_value_pairs指出了注解本身的类型以及键值对

RuntimeVisibleParameterAnnotations 

RuntimeInvisibleParameterAnnotations   属性

保存标注在对应方法的形式参数声明上面的注解的运行时可见状态分为可见和不可见两种num_parameters  形参个数parameter_annotations 每个元素表示一个形式参数的运行时注解  第 n项,表示方法描述符中的第  n  个形式参数
RuntimeVisibleParameterAnnotations_attribute {    u2 attribute_name_index;    u4 attribute_length;    u1 num_parameters;    {   u2         num_annotations;        annotation annotations[num_annotations];    } parameter_annotations[num_parameters];}
RuntimeInvisibleParameterAnnotations_attribute {    u2 attribute_name_index;    u4 attribute_length;    u1 num_parameters;    {   u2         num_annotations;        annotation annotations[num_annotations];    } parameter_annotations[num_parameters];}

AnnotationDefault   属性

如果一个method_info 是用来表述注解类型中的元素的该结构体的属性表中最多只能有一个AnnotationDefaultAnnotationDefault属性记录了由method_info 结构所表示的那个元素的默认值default_value 表示由AnnotationDefault属性外围的method_info结构所描述的那个注解类型元素的默认值说白了这个结构有用的才是default_value
AnnotationDefault_attribute {    u2            attribute_name_index;    u4            attribute_length;    element_value default_value;}

MethodParameters    属性

形参相关的一些信息 比如参数名称parameters_count  表示:本属性外围method_info 结构里面的descriptor_index、所引用的那个方法描述符中,有多少个参数描述符。parameters  表示实际的参数;name_index 要么是0,要么指向CONSTANT_Utf8_info ,表示一个有效的非限定名 用来指代某个形式参数。
access_flags ACC_FINAL 0x0010 形参为final 
ACC_SYNTHETIC 0x1000 形参没有显式或者隐式的在源代码中声明,由编译器生成
ACC_MANDATED 0x8000 形参是隐式声明,也就是编程语言规范对所有的编译器的要求必须生成
MethodParameters_attribute {    u2 attribute_name_index;    u4 attribute_length;    u1 parameters_count;    {  u2 name_index;        u2 access_flags;    } parameters[parameters_count];}

sourceFile  属性

class文件的源文件名,属性是可选的可以关闭但是一旦关闭,当抛出异常时,不会显示出错代码所归属的文件名称。
SourceFile_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 sourcefile_index;}
sourcefile_index 指向的是常量池中的CONSTANT_Utf8_info 表示源文件的文件名 。

SourceDebugExtension   属性

一个classFile只能包含一个属性,debug_extension  用于保存扩展调试信息,扩展调试信息对于java虚拟机来说没有实际的语义 。扩展信息使用改进版的UTF8 编码的字符串,也可以说这个就算是另一种格式的用于表示字符串的结构 。不过他比String类的实例所能表示的字符串更长。
SourceDebugExtension_attribute {    u2 attribute_name_index;    u4 attribute_length;    u1 debug_extension[attribute_length];}

Code属性的属性表中LineNumberTable   属性

源文件中给定的行号表示的内容对应字节码指令的行号(偏移量)之间的关系并不是运行时必须信息,但是会默认生成到Class文件中。可以通过参数设置不生成,但是程序运行产生的最主要影响就是当抛出异常时,堆栈中不会显示出错的行号,调试时也无法设置断点
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];}

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

LocalVariableTypeTable  属性

与LocalVariableTable类似,就是descriptor_index  被替换为了signature_index。主要针对泛型场景。非泛型签名与描述符基本一致,引入泛型后实际的泛型信息会被擦除 ,描述符就不足够准确了。
LocalVariableTypeTable_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 local_variable_type_table_length;    {  u2 start_pc;        u2 length;        u2 name_index;        u2 signature_index;        u2 index;    } local_variable_type_table[local_variable_type_table_length];}

Deprecated   属性

语义同语法中的@Deprecated  形式类似Synthetic   标志属性  有和没有的区别
attribute_length 固定为0
Deprecated_attribute {    u2 attribute_name_index;    u4 attribute_length;}

class文件的最详细的介绍,自然是官方文档https://docs.oracle.com/javase/specs/index.html包含多个版本的JDK  ,以及两种格式JVM|class文件浅析(二)_java_03

https://mp.weixin.qq.com/s/MLIZwMxzJuyv-36JmcFPGg