《深入理解Java虚拟机》读书笔记3

一、平台无关性和语言无关性

        字节码(ByteCode)是Java构建平台无关性和语言无关性的基石。

        平台无关性是指不同的CPU指令集、不同的操作系统,都能识别相同的字节码,实现“一次编写,到处运行(Write Once, Run Anywhere)”。

        语言无关性是指不同的开发语言编译的Class文件,只要符合Class文件应有的结构就可以在Java虚拟机上运行。

        java正是为了构建平台无关性和语言无关性,所以才将Java规范分为:Java语言规范《The Java Language Specification》和Java虚拟机规范《The Java Virtual Machine Specification》。

        Java语言无关性的示意图:

《《深入理解Java虚拟机》读书笔记3》

二、类文件结构

      Class文件是一组以8位字节为基础单位的二进制流。大于8字节的数据项,高位在前地位在后。

      Class文件中只有两种数据类型:无符号数和表

      其中,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以描述数字、索引引用、数量值、UTF-8编码构成的字符串值。

表用于描述有层次关系的复合结构的数据,类型有:cp_info、field_info、method_info、attribute_info

注:描述同一类型但数量不定的数据时,使用一个前置容量计数器加若干个连续的数据项的形式。

1、魔数

     每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否是一个能被虚拟机接受的Class文件。(身份识别)

     Class文件的魔数值为:0xCAFEBABE (“咖啡馆宝贝”)。

2、Class文件的版本号

     紧接着魔数的4个字节存储Class文件的版本号,其中第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。

注:高版本的JDK能向下兼容以前的版本的Class文件,但不能运行以后版本的Class文件。Java版本号从45开始,JDK1.0~1.1使用了45.0~45.3的版本号,JDK1.2能使用46.0~46.65535的版本号,同理JDK1.6能使用50.0~50.65535的版本号。

3、常量池

     紧接着主次版本号之后的是常量池入口,由于常量池中的常量的数量是不固定的,所以常量池入口放置的是一项u2类型的数据,代表常量池容量计数值(constant_pool_count),该容量计数从1开始,常量池中常量数等于计数值减一,索引为0的常量做特殊用处。

注:Class文件中只有常量池的容量计数是从1开始的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都是从0开始的。

       【】 常量池之中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

       字面量:接近于Java语言层面的常量概念,比如文本字符串、被声明为final的常量值等。

       注:对于字面常量,常量池不仅会存储该类本身定义的字面常量,还会存放该类引用到的其他类的字面常量,这样做是对访问的一种优化,比如笔记4中“被动引用”的第三个例子。

       符号引用包含以下三类常量:

              ** 类和接口的全限定名(Fully Qualified Name)

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

              ** 方法的名称和描述符

        【】常量池中的每一项常量都是一个表,表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型,共有11中结构各不相同的表结构数据,类型如下:

《《深入理解Java虚拟机》读书笔记3》

11种数据类型的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

《《深入理解Java虚拟机》读书笔记3》

4、访问标志

      在常量池之后,紧接着的2个字节代表访问标志(access_flags),这个标识用于识别一些类或接口层次的访问信息。

标志位及标志的含义如下:

《《深入理解Java虚拟机》读书笔记3》

注:access_flags中一共有32个标志位可以使用,当前只定义了其中的8个,没有使用到的标志位要求一律为0。

5、类索引、父类索引和接口索引集合

      紧接着访问标志之后的是按顺序排列的类索引(this_class)、父类索引(super_class)、接口索引集合(interfaces)。

      类索引和父类索引都是一个u2类型的数据。

      接口索引集合是一组u2类型的数据的集合。

      Class文件由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名(注:除了Object类外,所有Java类的父类索引都不为0)。接口索引集合用来描述这个类实现了哪些接口(实现的接口会按照声明顺序从左到右排列在接口的索引集合中)。

      类索引、父类索引引用的u2类型的索引值分别指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。类索引查找全限定名的过程如下:

《《深入理解Java虚拟机》读书笔记3》

        接口索引集合,入口的第一项是u2类型的数据代表接口计数器(interfaces_count),表示索引表的容量。

6、字段表集合

       字段表(field_info)用于描述接口或类中声明的变量。字段包含了类级变量或实例级变量,但不包括在方法内部声明的变量。(注意每个字段就是一张表)

       字段可以包含的信息:字段的访问权限、是否是static变量(类级地)、是否是可变的(final)、并发可见性(volatile)、是否可序列化(transient)、字段数据类型(基本类型、对象、数组)、字段名称。

       字段表结构定义:

《《深入理解Java虚拟机》读书笔记3》

       access_flags表示字段访问标记,其定义如下:

《《深入理解Java虚拟机》读书笔记3》 

        name_index是对常量池的引用,代表字段的简单名称。简单名字是相对于全限定名而言的。

        descriptor_index也是对常量池的引用,代表字段和方法的描述符。

        方法和字段的描述符是用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)和返回值。其描述规则如下:

【类型规则】:

         * 基本数据类型 和 void类型 都用一个大写字符来表示;

         * 对象类型用L加对象的全限定名来表示;

         * 数组类型,每一维度将使用一个前置的“[”字符来描述。例如定义一个java.lang.String[][]类型的二维数组,将被记录为:“[[Ljava/lang/String”,一个int[]类型,将被记录为“[I”.

注:类型描述符标识字符定义如下:

 《《深入理解Java虚拟机》读书笔记3》

       attributes_count、attribute_info,表示字段的属性表集合,用于存储一些额外的信息。

       字段表集合的第一个u2类型数据为容量计数器fields_count,,接着是一系列的字段表。

       字段表集合中不会列出从超类或父接口中继承的字段,但可能列出原本Java代码中不存在的字段,比如内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

7、方法表集合

      方法表的结构和字段表一样,各个结构的含义类似,只有访问标志、属性表集合的可选项有所不同。

方法表标识位的含义如下:

《《深入理解Java虚拟机》读书笔记3》

方法描述符的表示

【方法描述规则】:

        * 先描述参数列表,在描述返回值;

        * 参数列表按照参数的严格顺序放在一组小括号“()内。如方法void inc()的描述符为“()V”,方法java.lang.String toString()的描述符为“()Ljava/lang/String;”(注:其中“;”表示全限定名结束)。方法int indexOf(char[] source, int offset)的描述符为“([CI)I”。

8、属性表集合

      属性表(attribute_info)用于描述某些场景专有的信息,Class文件本身、字段表、方法表中都有自己的属性表集合。

      属性表不要求严格的顺序,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,java虚拟机会忽略掉它不认识的属性。

虚拟机规范预定义的属性如下:

《《深入理解Java虚拟机》读书笔记3》

       对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示。

属性表的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

<1>【Code属性表】

        Java程序方法体里面的代码经过编译器处理之后,最终会变成字节码指令,这些指令存储在Code属性表内,Code属性可以出现在方法表的属性集合中,接口或抽象类的方法不存在Code属性。

       Code属性表的结构如下:

《《深入理解Java虚拟机》读书笔记3》

       attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,代表该属性的属性名称;

       attribute_length指示属性值的长度。属性值的长度 固定为整个属性表的长度减去6个字节(注:这6个字节是指attribute_name_index和attribute_length项的长度总和)。

       max_stack:代表操作数栈(Operand Stacks)深度的最大值。注:虚拟机运行时需要该值来分配栈帧(Frame)中的操作栈深度。

       max_locals:代表局部变量表所需的存储空间,max_locals的单位是Slot。

注:Slot是虚拟机为局部变量分配内存所使用的最小单位,对于长度不超过32位的数据类型,每个局部变量占用1个Slot,而double和long这两种64位的数据类型则需要2个Slot来存放。

注:方法参数(包含实例方法隐含的this)、显式异常处理器参数、方法体中定义的局部变量都使用局部变量来存放。

       code_length和code用来存储字节码指令。code_lengh代表字节码长度,code是存储字节码指令的一系列字节流。

注:每条指令都是一个u1类型的单字节。

注:虚拟机规范限制了一个方法不能超过65535条字节码指令。超过了,Javac编译器就会拒绝编译。

       execption_table_length和exception_table是显式异常处理表集合,显式异常处理表对于Code属性来说并不是必须存在的。

显式异常处理表结构的定义如下:

《《深入理解Java虚拟机》读书笔记3》

这些字段的含义是:如果字节码从第start_pc行(注:这里的“行”,是指字节码相对于方法体开始的偏移量)到第end_pc行之间(不包含第end_pc行)出现了类型为catch_type代表的异常或其子类型的异常,则转到第handler_pc行继续处理。当catch_type的值为0时,代表任何的异常情况都需要转向到handler_pc处进行处理。

<2>【Exceptions属性】

Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Exceptions),也就是方法描述时在throws关键字后面列举的异常。

Exceptions属性的结构如下:

《《深入理解Java虚拟机》读书笔记3》

number_of_exception项表示可能抛出的受查异常数量;

exception_index_table项表示一个指向常量池中CONSTANT_Class_info型常量的索引,代表该受查异常的类型。

<3>【LineNumberTable属性】

        LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它不是运行时必需的属性。在Javac中使用-g:none或-g:lines选项来取消或要求生成这项信息。如果不生成这项信息,对程序运行产生的最主要的影响就是在抛出异常时,堆栈中将不会显示出错的行号,调试时无法按照源码来设置断点。

LineNumberTable属性的结构如下:

《《深入理解Java虚拟机》读书笔记3》

其中line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包括了start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源代码行号。

<4>【LocalVariableTable属性】

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源代码中定义的变量之间的关系。不是运行时必需的属性,默认也不会生成到Class文件之中,可以在Javac中使用-g:none或-g:vars选项来控制。

LocalVariableTable属性的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

其中loca_variable_info项代表一个栈帧与源码中的局部变量的关联,其结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

其中,start_pc和length属性分别代表这个局部变量的声明周期开始的字节码偏移量以及其作用范围覆盖的长度,(两者合起来就是局部变量在字节码中的作用域范围);

        name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称及该局部变量的描述符。

        index是这个局部变量在栈帧局部变量表中Slot的位置。当该变量为64位类型时,它占用的是Slot为index和index+1两个位置。

注:Java引入泛型后,增加了”LocalVariableTypeTable”属性。结构与LocalVariableTable类似,只有一个属性项不同,就是将descriptor_index替换为Signature(字段的特征签名)。

<5>【SourceFile属性】

        SourceFile属性用于记录生成这个Class文件的源码文件名称。该属性也是可选的,使用Javac的-g:none或-g:source选项来控制。

该属性的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

其中,sourcefile_index项是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名。

<6>【ConstantValue属性】

ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。

       Java中对于非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>方法中进行的;对于类变量,则有两种方式可以选择:在类构造器<clinit>方法中进行赋值,或者使用ConstantValue属性来赋值。Sun Javac编译器的选择是:对于final和static同时修饰,并且类型为基本类型或String类型的变量,生成ConstantValue属性来初始化,其余情况的static变量通过<clinit>方法来初始化。

ConstantValue属性的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

constantvaue_index项代表了常量池中一个字面量常量的引用,根据字段类型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info和CONSTANT_String_info常量中的一种。

<7>【InnerClasses属性】

InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它及它所包含的内部类生成InnerClasses属性。

InnerClasses属性的结构如下:

《《深入理解Java虚拟机》读书笔记3》

数据项number_of_classes代表需要记录多少个内部类信息,每个内部类的信息都由一个inner_classes_info表进行描述。

inner_classes_info表的结构如下:

《《深入理解Java虚拟机》读书笔记3》

inner_class_info_index和outer_info_index都是指向常量池中CONSTANT_Class_info型常量的索引,分别代表了内部类和宿主类的符号引用。

inner_name_index是指向常量池中CONSTANT_Utf8_info型常量的索引,代表这个内部类的名称,如果是匿名内部类,则这项值为0。

inner_class_access_flags是内部类的访问标志,类似于类的access_flags,它的取值范围如下:

《《深入理解Java虚拟机》读书笔记3》

<8>【Deprecated及Synthetic属性】

      Deprecated和Synthetic两个属性都属于标志类型的布尔属性,只存在有或者没有的区别,没有属性值的概念。

      Deprecated属性用于表示某个类、字段或方法,已经被程序作者定为不再推荐使用(在代码中使用@deprecated注解来设置)。

      Synthetic属性代表此字段或方法并不是有Java源码直接产生的,而是由编译器自行添加的。

Deprecated和Synthetic属性的结构定义如下:

《《深入理解Java虚拟机》读书笔记3》

其中attribute_length数据项的值必须为0x00000000。



    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/xiaoxiaoyusheng2012/article/details/52985657
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞