一、Java平台结构图
二、JVM、JRE和JDK关系
JVM:Java Virtual Machine(Java虚拟机),负责执行符合规范的Class文件
JRE: Java Runtime Environment (java运行环境),包含JVM和类库
JDK: Java Development Kit(java开发工具包),包含JRE和开发工具包,例如javac、javah
三、JVM所处的位置
我们通常工作中所接触的基本是Java库和应用以及Java核心类库,知晓如何使用就可以了,但是归根结底代码都是要编译成class文件由Java虚拟机执行的,所产生的结果或者现象都可以通过Java虚拟机的运行机制来解释。一些相同的代码会由于虚拟机的实现不同而产生不同结果。
四、Class文件格式
编译后被Java虚拟机所执行的代码使用了一种平台中立(不依赖于特定硬件及操作系统的)的二进制格式来表示,并且经常(但并非绝对)以文件的形式存储,因此这种格式被称为Class文件格式。Class文件格式中精确地定义了类与接口的表示形式,包括在平台相关的目标文件格式中一些细节上的惯例,例如字节序(Byte Ordering)等。
正如概念所说,Java为了能够实现平台无关性,制定了一套自己的二进制格式,并经常以文件的方式存储,称为Class文件。这样在不同平台上,只要都安装了Java虚拟机,那么都可以运行相同的Class文件。
五、数据类型
与Java程序语言中的数据类型相似,Java虚拟机可以操作的数据类型可分为两类:原始类型(Primitive Types,也经常翻译为原生类型或者基本类型)和引用类型(Reference Types)。与之对应,也存在有原始值(Primitive Values)和引用值(Reference Values)两种类型的数值可用于变量赋值、参数传递、方法返回和运算操作。
基本类型和引用类型的具体情况见下图:
Java虚拟机希望更多的类型检查放在编译期就完成,在运行期不需要进行这些操作。其中基本类型达到了这样的要求,在运行期间不需要对其进行类型检查,也不用和引用类型区分开。这是通过虚拟机的字节码指令完成的,不同类型的字节码指令中都包含了相应的数据类型。
整形类型和整型值的取值范围如下:
byte类型,取值范围是从-128至127(-27至27-1),包括-128和127。
short类型,取值范围是从−32768至32767(-215至215-1),包括−32768和32767。
int类型,取值范围是从−2147483648至2147483647(-231至231-1),包括−2147483648和2147483647。
long类型,取值范围是从−9223372036854775808至9223372036854775807(-263至263-1),包括−9223372036854775808和9223372036854775807。
char类型,取值范围是从0至65535,包括0和65535。
浮点类型、取值集合和浮点值:
浮点类型包含32位单精度的float类型和64位双精度的double类型两种,浮点数除了包括正负带符号可数的数值,还包括了正负零、正负无穷大和一个特殊的“非数字”标识(Not-a-Number,下文用NaN表示)。NaN值用于表示某些无效的运算操作,例如除数为零等情况。所有Java虚拟机的实现都必须支持两种标准的浮点数值集合:单精度浮点数集合和双精度浮点数集合。
returnAddress类型和值:
returnAddress类型会被Java虚拟机的jsr、ret和jsr_w指令所使用。returnAddress类型的值指向一条虚拟机指令的操作码。与前面介绍的那些数值类的原始类型不同,returnAddress类型在Java语言之中并不存在相应的类型,也无法在程序运行期间更改returnAddress类型的值。
boolean类型:
Java虚拟机不提供操作boolean类型的字节码指令,程序在编译后boolean类型都转化成了int操作。但是Java虚拟机支持boolean类型的数组的访问和修改,共用byte类型数组的字节码指令。
六、运行时数据区
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
Java虚拟机的逻辑构成
可以看出Java虚拟机的运行时数据区包括了:方法区、Java堆、Java虚拟机栈、PC寄存器、本地方法栈。
方法区
方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。
方法区可能发生如下异常情况:
如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常.
运行时常量池:
运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池在方法区中。
在创建类和接口的运行时常量池时,可能会发生如下异常情况:
当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那Java虚拟机将会抛出一个OutOfMemoryError异常。
Java堆
Java堆在虚拟机启动的时候被创建,Java堆主要用来为类实例对象和数组分配内存。Java虚拟机规范并没有规定对象在堆中的形式。
Java堆可能发生如下异常情况:
如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常。
Java虚拟机栈
每个Java虚拟机线程都有自己的Java虚拟机栈。Java虚拟机栈用来存放栈帧,而栈帧主要包括了:局部变量表、操作数栈、动态链接。
Java虚拟机使用局部变量表来完成方法调用时的参数传递。局部变量表的长度在编译期已经决定了并存储于类和接口的二进制表示中,一个局部变量可以保存一个类型为boolean、byte、char、short、float、reference 和 returnAddress的数据,两个局部变量可以保存一个类型为long和double的数据。
Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果。
每个栈帧中都包含一个指向运行时常量区的引用支持当前方法的动态链接。在Class文件中,方法调用和访问成员变量都是通过符号引用来表示的,动态链接的作用就是将符号引用转化为实际方法的直接引用或者访问变量的运行是内存位置的正确偏移量。
总的来说,Java虚拟机栈是用来存放局部变量和过程结果的地方。
Java虚拟机栈可能发生如下异常情况:
如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。
如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。
PC寄存器
每个Java虚拟机线程都有自己的PC寄存器。在某个线程被新建时,会获得一个PC寄存器。线程当前执行的方法称为当前方法,PC寄存器用来存放当前方法中当前执行的字节码指令的地址,如果当前方法是本地方法(Native),那么寄存器存放undefined。寄存器的大小至少应该能够存放一个returnAddress类型的数据或者与平台相关的本地指针的值。
本地方法栈
本地方法栈用于支持native方法的运行。