共计 5466 个字符,预计需要花费 14 分钟才能阅读完成。
由 Java 虚拟机执行的编译代码应用与硬件和操作系统无关的二进制格局示意,通常存储在 class 文件 中。class 文件 准确地定义了类或接口的示意模式,包含在特定于平台的指标文件格式中可能被视为天经地义的字节排序等细节。
数据类型
与 Java 编程语言一样,Java 虚拟机对两种类型进行操作:根本类型 和 援用类型。相应地,有两种类型的值能够存储在变量中,作为参数传递,由办法返回,并对其进行操作:根本值 和 援用值。
Java 虚拟机冀望简直所有类型查看都在运行时之前实现,通常由编译器实现,而不用由 Java 虚拟机自身实现。根本类型的值不须要被标记,也不须要在运行时查看以确定它们的类型,或者与援用类型的值辨别开来。相同,Java 虚拟机的指令集应用对特定类型的值进行操作的指令来辨别其操作数类型。例如,iadd、ladd、fadd 和 dadd 都是增加两个数值并产生数值后果的 Java 虚拟机指令,但每个指令都专门针对其操作数类型: int、long、float 和 double。
Java 虚拟机蕴含对对象的显式反对。对象要么是动态分配的类实例,要么是数组。对对象的援用被认为具备 Java 虚拟机类型援用。能够将援用类型的值视为指向对象的指针。一个对象可能存在多个援用。对象总是通过援用类型的值进行操作、传递和测试。
根本类型和值
Java 虚拟机反对的根本数据类型有 数值类型、布尔类型 和 returnAddress 类型。
数值类型由 整数类型 和 浮点类型 组成:
整数类型包含:byte、short、short、int、long 和 char。
浮点类型包含:float 和 double。
布尔类型 的值将 true 和 false 编码,默认值为 false。
returnAddress 类型 的值是指向 Java 虚拟机指令操作码的指针。在根本类型中,只有 returnAddress 类型与 Java 编程语言类型没有间接关联。
援用类型和值
有三种援用类型:类类型、数组类型 和 接口类型。它们的值别离是对动态创建的类实例、数组或实现接口的类实例或数组的援用。
数组类型 由具备单个维度的组件类型组成(其长度不禁类型给出)。数组类型 的组件类型自身能够是 数组类型。如果从任何 数组类型 开始,先思考其组件类型,而后再思考(如果也是数组类型)该类型的组件类型,依此类推,则最终必须达到不是 数组类型 的组件类型,这称为数组类型的元素类型。数组类型的元素类型必须是原始类型,类类型或接口类型。
援用值 也能够是非凡的空援用,一个对 no 对象的援用,在这里用 null 示意。空援用最后没有运行时类型,但能够转换为任何类型。援用类型的默认值是 null。
运行时数据区
Java 虚拟机定义了在程序执行期间应用的各种运行时数据区域。其中一些数据区域是在 Java 虚拟机启动时创立的,只有在 Java 虚拟机退出时才会销毁。其余数据区域是在每个线
程创立时创立,在线程退出时销毁。
程序计数器(Program Counter Register)
Java 虚拟机能够同时反对多个线程执行。每个 Java 虚拟机线程都有本人的 程序计数器。在任何时候,每个 Java 虚拟机线程都在执行单个办法的代码,即该线程的以后办法。如果该办法不是 native,程序计数器 蕴含以后正在执行的 Java 虚拟机指令的地址。如果线程以后执行的办法是 native,则 Java 虚拟机的 程序计数器 的值是未定义的。Java 虚拟机的 程序计数器 足够宽,能够包容特定平台上的 returnAddress 或本机指针。
Java 虚拟机栈
每个 Java 虚拟机线程都有一个公有的 Java 虚拟机栈,与线程同时创立。Java 虚拟机栈 存储 栈帧。Java 虚拟机栈相似于 C 等传统语言的栈:它持有局部变量和局部后果,并在办法调用和返回中起作用。因为除了 push 和 pop 栈帧 外,Java 虚拟机栈 素来没有被间接操作过,所以能够对栈帧进行堆调配。Java 虚拟机栈的内存不须要是间断的。
Java 虚拟机栈具备固定的大小,或者依据计算的须要动静扩大和膨胀。如果 Java 虚拟机栈的大小是固定的,则能够在创立栈时独立抉择每个 Java 虚拟机栈的大小。
Java 虚拟机实现能够为程序员或用户提供对 Java 虚拟机栈初始大小的管制,在动静扩大或膨胀 Java 虚拟机栈的状况下,还能够提供对最大和最小大小的管制。
下列异常情况与 Java 虚拟机栈相干:
如果线程中的计算须要比容许的更大的 Java 虚拟机栈,则 Java 虚拟机将抛出一个 StackOverflowError 异样。
如果能够动静地扩大的 Java 虚拟机栈,扩大时可用内存有余,或者内存不足以创立一个新线程的初始 Java 虚拟机栈,Java 虚拟机抛出一个 OutOfMemoryError 异样。
本地办法栈
Java 虚拟机的实现能够应用传统栈(俗称“C 栈”)来反对 native 办法(用 Java 编程语言以外的语言编写的办法)。和 Java 虚拟机栈 一样,本地办法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异样。
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的 堆。堆是为所有类实例和数组分配内存的运行时数据区域。
堆 是在虚拟机启动时创立的。对象的 堆 存储由主动存储管理零碎(称为 垃圾收集器)回收;对象从不显式开释。Java 虚拟机没有特定类型的主动存储管理零碎,能够依据实现者的零碎需要抉择存储管理技术。堆 的大小能够是固定的,也能够依据计算的须要进行扩大,如果不须要更大的堆,则能够膨胀。堆的内存不须要是间断的。
Java 虚拟机实现能够为程序员或用户提供对 堆 初始大小的管制,如果能够动静扩大或膨胀堆,还须要能够管制 堆 的最大和最小大小。
下列异常情况与 堆 相干:
如果计算须要的 堆 比主动存储管理零碎提供的 堆 多,则 Java 虚拟机抛出 OutOfMemoryError。
办法区
Java 虚拟机具备一个在所有 Java 虚拟机线程之间共享的 办法区。办法区 相似于惯例语言的编译代码的存储区域,或者相似于操作系统过程中的“文本”段。它存储每个类的构造,例如运行时常量池,字段和办法数据,以及办法和构造函数的代码,包含用于类和接口初始化以及实例初始化的非凡办法。
办法区 是在虚拟机启动时创立的。只管 办法区 在逻辑上是堆的一部分,然而简略的实现能够抉择不进行垃圾回收或压缩。没有规定· 办法区 的地位或用于治理已编译代码的策略。办法区能够是固定大小的,或者能够依据计算的须要进行扩大,如果不须要更大的办法区域,则能够放大。办法区的内存不用是间断的。
Java 虚拟机实现能够为程序员或用户提供对办法区 初始大小的管制,并且在办法区域大小可变的状况下,能够管制最大和最小 办法区 大小。
以下异样条件与 办法区 相关联:
如果无奈提供 办法区 中的内存来满足调配申请,则 Java 虚拟机将抛出一个 OutOfMemoryError。
运行时常量池
运行时常量池 是 class 文件 中 constant_pool 表的每个类或每个接口的运行时示意。它蕴含几种类型的常量,从编译时已知的数值常量到必须在运行时解析的办法和字段援用。运行时常量池 的性能相似于传统编程语言的符号表,只管它蕴含的数据范畴比典型的符号表更广。
每个 运行时常量池 都是从 Java 虚拟机的 办法区 调配的。类或接口的 运行时常量池 是在 Java 虚拟机创立类或接口时结构的。
下列异常情况与类或接口的运行时常量池的结构无关:
在创立类或接口时,如果构建运行时常量池所需的内存超过了 Java 虚拟机的 办法区 所能提供的内存,Java 虚拟机抛出 OutOfMemoryError。
栈帧
栈帧 用于存储数据和局部后果,以及执行动静链接、办法的返回值和调度异样。
每次调用一个办法都会创立一个新 栈帧。无论实现是失常的还是异样的(它抛出一个未捕捉的异样),当它的办法调用实现时,一个 栈帧 将被销毁。栈帧 是从创立 栈帧 的线程的 Java 虚拟机栈中调配的。每一帧都有本人的 局部变量 数组,本人的 操作数栈,以及对以后办法类的 运行时常量池 的援用。
能够应用附加的特定于实现的信息 (如调试信息) 对 栈帧 进行扩大。
局部变量数组 和 操作数栈 的大小是在编译时确定的,并随与 栈帧 关联的办法的代码一起提供。因而,栈帧 数据结构的大小只取决于 Java 虚拟机的实现,并且能够在办法调用时同时调配这些构造的内存。
在给定的控制线程中,只有一个帧 (执行办法的帧) 是流动的。这个帧称为以后帧,它的办法称为以后办法。定义以后办法的类是以后类。对 局部变量 和 操作数栈 的操作通常与以后帧相干。
如果一个帧的办法调用另一个办法,或者该帧的办法实现,则该帧将进行为以后帧。当调用一个办法时,将创立一个新 栈帧,并在管制转移到新办法时成为以后 栈帧。在办法返回时,以后帧将其办法调用的后果 (如果有的话) 传回前一帧。以后一帧成为以后帧时,以后帧将被抛弃。
留神,线程创立的 栈帧 是该线程的本地 栈帧,不能被任何其余线程援用。
局部变量
每一 栈帧 蕴含一组称为 局部变量 的变量。栈帧 的 局部变量 数组的长度在编译时确定,并以类或接口的二进制示意模式提供,同时提供与帧相干的办法的代码。
单个 局部变量 能够保留类型为 boolean、byte、char、short、int、float、reference 或 returnAddress 的值。一对 局部变量 能够蕴含 long 或 double 类型的值。
局部变量 通过索引寻址。第一个 局部变量 的索引为 0。当且仅当该整数比局部变量数组的大小小 0 到 1 之间时,该整数被认为是 局部变量 数组的索引。
long 或 double 类型的值占用两个间断的局部变量。这样的值只能应用较小的索引来解决。例如,在索引 n 处存储在局部变量数组中的 double 类型的值实际上占用了索引为 n 和 n+1 的局部变量;然而,无奈从索引 n+1 处加载局部变量。它能够存储。然而,这样做会使局部变量 n 的内容有效。
Java 虚拟机不须要 n 是偶数。直观地说,long 和 double 类型的值 在局部变量 数组中不用是 64 位对齐的。实现者能够自在决定应用为该值保留的两个 局部变量 来示意这些值的适当办法。
Java 虚拟机应用本地变量在办法调用时传递参数。在类办法调用中,任何参数都是在间断的 局部变量 中传递的,从 局部变量 0 开始。在实例办法调用中,总是应用局部变量 0 将援用传递给调用实例办法的对象(在 Java 编程语言中)。任何参数随后都在从局部变量 1 开始的间断局部变量中传递。
操作数栈
每个 栈帧 都蕴含一个后进先出(LIFO)堆栈,称为操作数栈。帧的操作数栈的最大深度是在编译时确定的,并与帧关联的办法的代码一起提供。
当上下文分明时,咱们有时会将以后帧的操作数栈简略地称为操作数栈。
创立蕴含操作数栈的 栈帧 时,该 操作数栈 为空。Java 虚拟机提供将常量或值从局部变量或字段加载到操作数栈的指令。其余 Java 虚拟机指令从操作数栈中获取操作数,对它们进行操作,并将后果推回操作数栈。操作数栈还用于筹备传递给办法的参数和接管办法后果。
例如,iadd 指令将两个 int 值相加。它要求将 int 值增加到 操作数栈 的前两个值中,这是由后面的指令推入的。两个 int 值都是从 操作数栈 中弹出的。它们被增加,它们的和被推回 操作数栈。子计算能够嵌套在 操作数栈 上,从而产生可由蕴含计算应用的值。
操作数栈 上的每个条目能够蕴含任何 Java 虚拟机类型的值,包含 long 或 double 类型的值。
操作数堆栈中的值必须以适宜其类型的形式操作。例如,不可能推两个 int 值,而后将它们视为 long 值; 也不可能推两个浮点值,而后用 iadd 指令将它们相加。大量的 Java 虚拟机指令(dup 指令和 swap)作为原始值对运行时数据区域进行操作,而不思考它们的具体类型; 这些指令的定义形式使它们不能用于批改或合成单个值。这些操作数栈操作的限度是通过类文件验证来施行的。
在任何时候,操作数堆栈都有一个关联的深度,其中 long 或 double 类型的值为深度奉献两个单位,其余类型的值为深度奉献一个单位。
动静链接
每一 栈帧 都蕴含对以后办法类型的 运行时常量池 的援用,以反对办法代码的 动静链接。办法的类文件代码援用要调用的办法和要通过符号援用拜访的变量。动静链接 将这些符号办法援用转换为具体的办法援用,依据须要加载类来解析尚未定义的符号,并将变量拜访转换为与这些变量的运行时地位相干的存储构造中的适当偏移量。
办法和变量的这种前期绑定使办法应用的其余类中的更改不太可能毁坏此代码。
办法失常调用实现
如果办法调用没有导致抛出异样,则办法调用失常实现,无论是间接从 Java 虚拟机抛出,还是执行显式抛出语句的后果。如果以后办法的调用失常实现,则可能向调用办法返回一个值。当被调用的办法执行一条返回指令时,就会产生这种状况,对于返回的值的类型(如果有的话),必须抉择一条返回指令。
在这种状况下,应用以后 栈帧 来复原调用程序的状态,包含它的局部变量 和 操作数栈,并适当减少调用程序的 程序计数器 以跳过办法调用指令。而后在调用办法的框架中持续失常执行,将返回的值 (如果有的话) 推入该 栈帧 的 操作数栈。
办法异样调用实现
如果在办法中执行 Java 虚拟机指令导致 Java 虚拟机抛出异样,并且该异样不在办法中解决,则办法调用将忽然完结。athrow 指令的执行也会导致显式抛出异样,如果以后办法没有捕捉异样,则会导致办法调用的异样实现。异样实现的办法调用永远不会向调用者返回值。
对象的示意
Java 虚拟机不强制要求对象具备任何特定的内部结构。
Oracle 对 Java 虚拟机的实现,对类实例的援用句柄指针,指针自身就是一对:一个蕴含对象办法表的指针和一个指向类对象的指针,示意对象的类型,以及其余为对象从堆中调配的内存数。