Java虚拟机规范读书笔记

        Java 虚拟机可以看作是一台抽象的计算机。如同真实的计算机那样,它有自己的指令集以及

各种运行时内存区域。

       Java 虚拟机并不局限于特定的实现技术、主机硬件和操作系统, Java 虚拟机也不局限于特定的

代码执行方式,它不强求使用解释器来执行程序,也可以通过把自己的指令集编译为实际 CPU 的

指令来实现,它可以通过微代码( Microcode)来实现,或者甚至直接实现在 CPU 中。

         Java 虚拟机与 Java 语言并没有必然的联系,它只与特定的二进制文件格式——Class 文件
格式所关联, Class 文件中包含了 Java 虚拟机指令集(或者称为字节码、 Bytecodes)和符号

表,还有一些其他辅助信息。

 1.PC 寄存器

Java 虚拟机可以支持多条线程同时执行,每一条 Java

虚拟机线程都有自己的 PC( Program Counter)寄存器。在任意时刻,一条 Java 虚拟机线程
只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法( Current
Method, §2.6)。如果这个方法不是 native 的,那 PC 寄存器就保存 Java 虚拟机正在执行的
字节码指令的地址,如果该方法是 native 的,那 PC 寄存器的值是 undefined。 PC 寄存器的容

量至少应当能保存一个 returnAddress 类型的数据或者一个与平台相关的本地指针的值。

2. Java 虚拟机栈

每一条 Java 虚拟机线程都有自己私有的 Java 虚拟机栈( Java Virtual Machine Stack)
①,这个栈与线程同时创建,用于存储栈帧( Frames, §2.6)。 Java 虚拟机栈的作用与传统语
言(例如 C 语言)中的栈非常类似,就是用于存储局部变量与一些过程结果的地方。另外,它在
方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外, Java 虚拟机栈不会
再受其他因素的影响,所以栈帧可以在堆中分配②, Java 虚拟机栈所使用的内存不需要保证是连

续的。, Java 虚拟机栈也被称为“ Java 栈”

3 Java 堆
在 Java 虚拟机中,堆( Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例
数组对象分配内存的区域。Java 堆在虚拟机启动的时候就被创建,它存储了被自动内存管理系统( Automatic Storage

Management System,也即是常说的“ Garbage Collector(垃圾收集器)”)所管理的各种对象,这些受管理的对象无需,也无法显式地被销毁。Java 堆所使用的内存不需要保证是连续的。

4.方法区

在 Java 虚拟机中,方法区( Method Area) 是可供各条线程共享的运行时内存区域。方法区与传统语言中的编译代码储存区( Storage Area Of Compiled Code)或者操作系统进程的正文段( Text Segment)的作用非常类似,它存储了每一个类的结构信息,例如运行时常量池( Runtime Constant Pool)、字段和方法数据构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法( §2.9)。

方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集。这个版本的 Java 虚拟机规范也不限定实现方法区的内存位置和编译代码的管理策略。方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。 方法区在实际内存空间中可以是不连续的

5 运行时常量池

运行时常量池( Runtime Constant Pool)是每一个类或接口的常量池( Constant_Pool,§4.4)的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表( SymbolTable)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。每一个运行时常量池都分配在 Java 虚拟机的方法区之中( §2.5.4),在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来.

6 本地方法栈

Java 虚拟机实现可能会使用到传统的栈(通常称之为“ C Stacks”)来支持 native 方法( 指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈( Native MethodStack)。如果 Java 虚拟机不支持 natvie 方法,并且自己也不依赖传统栈的话,可以无需支持本地方法栈,如果支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配


栈帧
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接( Dynamic Linking)、方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出

了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间分配在 Java 虚拟机栈之中,每一个栈帧都有自己的局部变量表操作数栈指向当前方法所属的类的运行时常量池的引用。

因此,栈帧容量的大小仅仅取决于 Java 虚拟机的实现和方法调用时可被分配的内存

在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈帧( Current Frame),这个栈帧对应的方法就被称为是当前方法( Current Method),定义这个方法的类就称作当前类( Current Class)。对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的对局部变量表和操作数栈进行的操作。如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。当一个新的方法被调用,一个新的栈帧也会随之而创建,并且随着程序控制权移交到新的方法而成为新的当前栈帧。当方法返回的之际,当前栈帧会传回此方法的执行结果给前一个栈帧,在方法返回之后,当前栈帧就随之被丢弃,前一个栈帧就重新成为当前栈帧了,栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧

 局部变量表
每个栈帧( §2.6)内部都包含一组称为局部变量表( Local Variables)的变量列表。栈
帧中局部变量表的长度由编译期决定,并且存储于类和接口的二进制表示之中,既通过方法的
Code 属性( §4.7.3)保存及提供给栈帧使用

一个局部变量可以保存一个类型为 boolean、 byte、 char、 short、 float、 reference
和 returnAddress 的数据,两个局部变量可以保存一个类型为 long 和 double 的数据。

Java 虚拟机使用局部变量表来完成方法调用时参数传递,当一个方法被调用的时候,它的
参数将会传递至从 0 开始的连续的局部变量表位置上。特别地,当一个实例方法调用的时候,
第 0 个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即 Java 语言中的“ this”
关键字)。后续的其他参数将会传递至从 1 开始的连续的局部变量表位置上


每一个栈帧( §2.6)内部都包含一个称为操作数栈( Operand Stack)的后进先出( Last-In-First-Out, LIFO)栈。在上下文明确,不会产生误解的前提下,我们经常把“当前栈帧的操作数栈”直接简称为“操作数栈,在任意时刻,操作数栈都会有一个确定的栈深度,一个 long 或者 double 类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度.

动态链接

每一个栈帧( §2.6)内部都包含一个指向运行时常量池( §2.5.5)的引用来支持当前方法
的代码实现动态链接( Dynamic Linking)。在 Class 文件里面,描述一个方法调用了其他方法,
或者访问其成员变量是通过符号引用( Symbolic Reference)来表示的,动态链接的作用就是
将这些符号引用所表示的方法转换为实际方法的直接引用

在一些 Oracle 的 Java 虚拟机实现中,指向对象实例的引用是一个指向句柄的指针,这个句柄包含两部分
信心,一部分是指向这个对象所包括的方法表以及指向这个对象所属类相关的信息;另一部分是指向在堆中分配的

对象实例数据。

 对于简单的 Java 虚拟机实现,可以把异步异常简单地放在程序控制权转移指令上处理。因为程序终归是有
限的,总会遇到控制权转移的指令,所以异步异常抛出的延迟时间也是有限的。如果能保证在控制权转移指令之间
的代码没有异步异常抛出,那代码生成器就可以有相当的灵活性进行指令重排序优化来获取更好的性能。

Java 虚拟机本身不会对方法的对异常处理器表做排序或者其他方式的强制处理,
所以 Java 语言中对异常处理的语义,实际上是通过编译器适当安排异常处理器在表中的顺序来协
助完成的。在 Class 文件中定义了明确的异常处理器查找顺序,才能保证无论 Class 文件是通过

何种途径产生的, Java 虚拟机执行时都能有一致的行为表现。

类( static) 方法不需要传递实例引用,所以它们不需要使用第 0 个局部变量来保存 this
关键字。

对普通实例方法调用是在运行时根据对象类型进行分派的




猜你喜欢

转载自blog.csdn.net/weixin_41888669/article/details/79691980