《深入理解java虚拟机》读书笔记 第六章 类文件结构

jvm的语言无关性

时至今日,商业机构和开源机构已经在Java语言之外发展出一大批在Java虚拟机之上
运行的语言,如Clojure、Groovy、 JRuby、 Jython、 Scala 等。
Java虚拟机只与"Class这种二进制文件"绑定。

在这里插入图片描述

Class类文件的结构

Class文件是一组以8位字节为基础单位的二进制流。
当遇到超过8位的数据项时,则会按照高位在前(大端)方式分割后存储。

Class文件以一种类似于C语言结构体的伪结构存储。
这种伪结构只有两种数据类型:
    1. "无符号数"
        基本的数据类型,以u1、u2、u4、u8分别表示1个字节、2个字节、4个字节和8个字节的无符号数。
    2. "表"
        由无符号数组成的复合数据类型,习惯性以"_info"结尾。整个Class文件本质上就是一张表。由下图所示数据项构成。
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时称这一系列连续的某一类型的数据为某一类型的集合。(如图中的*_count)

可以使用jdk自带的"javap工具"来辅助分析。

在这里插入图片描述

魔数(Magic Number)与Class文件版本

作用是确定这个文件是否为能被虚拟机接受的Class文件。Java魔数被设置为0xCAFEBABE。
紧接着,是u2的minor_version(次要版本号)。
后方是u2的major_version(主要版本号)。
java版本从45开始。

常量池(constant_pool_count)

"常量池是Class文件空间最大的数据项目之一"
"常量池容量计数是以1开始"

主要分为两大类常量:
    1. 字面量(Literal)
    2. 符号引用(Symbolic References)
        属于编译原理方面的概念。包括三类常量:
            1. 类和接口的全限定名(Fully Qualified Name)
            2. 字段的名称和描述符(Descriptor)
            3. 方法的名称和描述符

访问标志(access_flags)

访问标志用于识别一些类或者接口层次的访问信息。包括:
    1. 是类还是接口
    2. 是否为public类型
    3. 是否为abstract
    4. 是否final

在这里插入图片描述

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

类索引和父类索引都是"一个u2类型的数据"。
接口索引集合是"一组u2类型的数据的集合"。
由于Java语言不允许多重继承,所以父类索引只有一个。
接口索引集合则是其继承或实现的接口集合

字段表集合

方法表集合

属性表集合

Code

Exceptions

LineNumberTable

LocalVariableTable

LocalVariableTypeTable

SourceFile

ConstantValue

作用:通知虚拟机自动为静态变量赋值

InnerClass

Deprecated 及 Synthetic

StackMapTable

Signature

为了"弥补java擦除法实现的假泛型"

BootstrapMethods

字节码指令

不考虑异常时的最基本执行模型
在这里插入图片描述

字节码与数据类型

i -> int
l -> long
s -> short
b -> byte
f -> float
a -> reference

记载和存储指令

将一个局部变量加载到操作栈:load、load_<n>

将一个数值从操作数栈存储到局部变量表:store、store_<n>

将一个常量加载到操作数栈。push、ldc、const;

扩充局部变量表的访问索引的指令:wide。

运算指令

加法:add
减法:sub
乘法:mul
除法:div
求余:rem
取反:neg
位移:sh
按位或指令:or
按位与指令:and
按位异或指令:xor
局部变量自增指令:iinc
比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
各种类型的比较都会转为int的比较

类型转换指令

jvm 支持小范围类型向大范围类型的安全转换。
窄化(大范围向小范围)时,需要显式指令来完成,如i2b。

尽管数据类型窄化转换可能会发生上限溢出、下限溢出和精度丢失等情况,但是Java虚
拟机规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。

对象创建与访问指令

创建类实例的指令:new
创建数组的指令:newarray、anewarray、multianewarray。
访问类字段(static字段,或者称为类变量)和实例字段的指令:getfield、putfield、getstatic、putstatic。
把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。
将一个操作数栈的值存储到数组兀素中的指令: bastore、 castore、 sastore、 iastore、
fastore、dastore、 aastore。
取数组长度的指令: arraylength。
检查类实例类型的指令: instanceof、 checkcast。

操作数栈管理指令

将栈顶一个或两个元素出栈:pop、pop2
复制栈顶一个或两个数值并将复制值重新压入栈顶:dup
将栈顶最顶端的两个数值互换:swap

控制转移指令

从概念模型上,可以认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。
条件分支: ifeq、 iflt、ifle、 ifne、 ifgt、 ifge、 ifnull、 ifnonnull、 if_icmpeq、 if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。
复合条件分支: tableswitch、 lookupswitch。
无条件分支: goto、 goto_w、jsr、jsr_w、ret。

方法调用和返回指令

invokevirtual 用于调用对象的实例方法,根据对象的实例类型进行分派(虚方法分派),这也是java语言中最常见的方法分派方式。
invokeinterface 调用接口方法
invokespecial 调用一些需要特殊处理的实例方法,包括实例化初始方法、私有方法和父类方法
invokestatic 用于调用类方法(static)
invokedynamic 在运行时动态解析出调用点限定符所引用的方法,并执行该方法。

方法调用指令与数据类型无关,但返回指令区分类型。
ireturn、lreturn等,需要注意的是,"return是相对于void方法,实例初始化方法及类和接口的类初始化方法使用"

```shell
## 异常处理指令

throw语句 都由athrow指令来实现。除了throw显式抛出异常意外,java虚拟机规范还规定了许多运行时异常,比如当除数为0时,抛出ArithmeticException。
在jvm中,catch在以前是由字节码指令完成(jsr和ret),但现在是使用异常表。


## 同步指令
```shell
jvm 支持同步方法和方法内部一段指令序列的同步,两种同步结构都用Monitor来支持。
jvm 指令集中由moniterenter和monitorexit来支持synchronized关键字的语义。

同步方法中,如果出现异常,则此Monitor自动失效。在jvm中,编译器必须确保无论方法如何完成(正常完成、非正常完成、异常退出),都必须执行其对应的monitorexit指令。

公有设计和私有实现

公有设计:
    java虚拟机实现必须能够读取Class文件并实现包含在其中的代码的语义。
私有实现:
    满足虚拟机规范的约束下,虚拟机后台如何处理Class文件完全是实现者自己的事。

猜你喜欢

转载自blog.csdn.net/lik_lik/article/details/89277445
今日推荐