由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虚拟机退出时才会销毁。其余数据区域是在每一个线 程建立时建立,在线程退出时销毁。
Java 虚拟机能够同时支持多个线程执行。每一个 Java 虚拟机线程都有本身的 程序计数器
。在任什么时候候,每一个 Java 虚拟机线程都在执行单个方法的代码,即该线程的当前方法。若是该方法不是 native,程序计数器
包含当前正在执行的 Java 虚拟机指令的地址。若是线程当前执行的方法是 native,则 Java 虚拟机的 程序计数器
的值是未定义的。Java 虚拟机的 程序计数器
足够宽,能够容纳特定平台上的 returnAddress 或本机指针。
每一个 Java 虚拟机线程都有一个私有的 Java 虚拟机栈
,与线程同时建立。Java 虚拟机栈
存储 栈帧。Java 虚拟机栈
相似于 C 等传统语言的栈:它持有局部变量和部分结果,并在方法调用和返回中起做用。由于除了 push 和 pop 栈帧 外,Java 虚拟机栈
历来没有被直接操做过,因此能够对栈帧进行堆分配。Java虚拟机栈的内存不须要是连续的。
Java 虚拟机栈具备固定的大小,或者根据计算的须要动态扩展和收缩。若是 Java 虚拟机栈的大小是固定的,则能够在建立栈时独立选择每一个 Java 虚拟机栈的大小。
Java 虚拟机实现能够为程序员或用户提供对 Java 虚拟机栈
初始大小的控制,在动态扩展或收缩 Java 虚拟机栈
的状况下,还能够提供对最大和最小大小的控制。
下列异常状况与Java 虚拟机栈
相关:
Java 虚拟机栈
,则 Java 虚拟机将抛出一个 StackOverflowError
异常。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 虚拟机抛出 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 虚拟机的实现,对类实例的引用句柄指针,指针自己就是一对:一个包含对象方法表的指针和一个指向类对象的指针,表示对象的类型,以及其余为对象从堆中分配的内存数