JVM的每一个实例都有一个它本身的方法域和一个堆,运行于JVM内的全部的线程都共享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据所包含的类信息,并把它们放到方法域中;当程序运行的时候,JVM把程序初始化的全部对象置于堆上;而每一个线程建立的时候,都会拥有本身的程序计数器和 Java栈,其中程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方法调用的状态被存储在本地方法栈,该方法栈依赖于具体的实现。 java
2.1.1执行引擎 程序员
执行引擎处于JVM的核心位置,在Java虚拟机规范中,它的行为是由指令集所决定的。尽管对于每条指令,规范很详细地说明了当JVM执行字节码遇到指令时,它的实现应该作什么,但对于怎么作却言之甚少。Java虚拟机支持大约248个字节码。每一个字节码执行一种基本的CPU运算,例如,把一个整数加到寄存器,子程序转移等。Java指令集至关于Java程序的汇编语言。 算法
Java指令集中的指令包含一个单字节的操做符, 用于指定要执行的操做, 还有0个或多个操做数, 提供操做所需的参数或数据。许多指令没有操做数,仅由一个单字节的操做符构成。 数组
虚拟机的内层循环的执行过程以下: 安全
do{ 多线程
取一个操做符字节; 函数
根据操做符的值执行一个动做; spa
}while(程序未结束) .net
因为指令系统的简单性,使得虚拟机执行的过程十分简单,从而有利于提升执行的效率。指令中操做数的数量和大小是由操做符决定的。若是操做数比一个字节大,那么它存储的顺序是高位字节优先。例如,一个16位的参数存放时占用两个字节,其值为: 线程
第一个字节*256+第二个字节字节码。
指令流通常只是字节对齐的。指令tableswitch和lookup是例外,在这两条指令内部要求强制的4字节边界对齐。
2.1.2本地方法接口
对于本地方法接口,实现JVM并不要求必定要有它的支持,甚至能够彻底没有。Sun公司实现Java本地接口 (JNI) 是出于可移植性的考虑,固然咱们也能够设计出其它的本地接口来代替Sun公司的JNI。可是这些设计与实现是比较复杂的事情,须要确保垃圾回收器不会将那些正在被本地方法调用的对象释放掉。
2.1.3 Runtime data area
Runtime data area 主要包括五个部分:Heap (堆), Method Area (方法区域), Java Stack (java的栈), Program Counter (程序计数器), Native method stack (本地方法栈)。Heap 和Method Area是被全部线程的共享使用的, 用来保存各类JAVA对象,好比数组,线程对象等;而Java stack, Program counter 和Native method stack是以线程为粒度的,每一个线程独自拥有。
Heap
Java程序在运行时建立的全部类实例或数组都是从堆中分配空间,一个Java虚拟实例中只存在一个堆空间,所以全部线程都将共享这个堆。每个java程序独占一个JVM实例,于是每一个java程序都有它本身的堆空间,它们不会彼此干扰。可是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。 (这里可能出现的异常java.lang.OutOfMemoryError: Java heap space)
堆的管理是由垃圾回收来负责的,不给程序员显式释放对象的能力。Java不规定具体使用的垃圾回收算法, 能够根据系统的需求使用各类各样的算法。
Method area
在Java虚拟机中,被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,而后读入这个class文件内容并把它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。该类型中的类(静态)变量一样也存储在方法区中。与Heap 同样,method area是多线程共享的,所以要考虑多线程访问的同步问题。好比,假设同时两个线程都企图访问一个名为Lava的类,而这个类尚未内装载入虚拟机,那么,这时应该只有一个线程去装载它,而另外一个线程则只能等待。 (这里可能出现的异常java.lang.OutOfMemoryError: PermGen full)
Java方法区与传统语言中的编译后代码或是Unix进程中的正文段相似。它保存方法代码(编译后的java代码)和符号表。在当前的Java实现中,方法代码不包括在垃圾回收堆中,但计划在未来的版本中实现。每一个类文件包含了一个Java类或一个Java界面的编译后的代码。能够说类文件是Java语言的执行代码文件。为了保证类文件的平台无关性,Java虚拟机规范中对类文件的格式也做了详细的说明。其具体细节请参考Sun公司的Java 虚拟机规范。
Java stack
Java stack以帧为单位保存线程的运行状态。虚拟机只会直接对Java stack执行两种操做:以帧为单位的压栈或出栈。每当线程调用一个方法的时候,就对当前状态做为一个帧保存到java stack中(压栈);当一个方法调用返回时,从java stack弹出一个帧(出栈)。
Java栈是与每个线程关联的,JVM在建立每个线程的时候,会分配必定的栈空间给线程。它主要用来存储线程执行过程当中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。
栈的大小是有必定的限制,若是在线程执行的过程当中,栈空间不够用,那么JVM就会抛出StackOverFlow异常,这种状况通常是死递归形成的。下面的程序能够说明这个问题。
public class TestStackOverFlow { public static void main( String[] args ) { Recursive r = new Recursive (); r.doit( 10000 ); // Exception in thread "main" java.lang.StackOverflowError } } class Recursive { public int doit( int t ) { if( t <= 1 ) { return 1; } return t + doit( t - 1 ); } }
Java虚拟机的栈有三个区域: 局部变量区、运行环境区、操做数区。
局部变量区:每一个Java方法使用一个固定大小的局部变量集。它们按照与vars寄存器的字偏移量来寻址。局部变量都是32位的。长整数和双精度浮点数占据了两个局部变量的空间,却按照第一个局部变量的索引来寻址。(例如, 一个具备索引n的局部变量, 若是是一个双精度浮点数, 那么它实际占据了索引n和n+1所表明的存储空间) 虚拟机规范并不要求在局部变量中的64位的值是64位对齐的。虚拟机提供了把局部变量中的值装载到操做数栈的指令,也提供了把操做数栈中的值写入局部变量的指令。
运行环境区:在运行环境中包含的信息用于动态连接,正常的方法返回以及异常捕捉。
动态连接:运行环境包括对指向当前类和当前方法的解释器符号表的指针,用于支持方法代码的动态连接。方法的class文件代码在引用要调用的方法和要访问的变量时使用符号。动态连接把符号形式的方法调用翻译成实际方法调用,装载必要的类以解释尚未定义的符号,并把变量访问翻译成与这些变量运行时的存储结构相应的偏移地址。动态连接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码。
正常的方法返回:若是当前方法正常地结束了,在执行了一条具备正确类型的返回指令时,调用的方法会获得一个返回值。执行环境在正常返回的状况下用于恢复调用者的寄存器,并把调用者的程序计数器增长一个恰当的数值,以跳过已执行过的方法调用指令,而后在调用者的执行环境中继续执行下去。
异常捕捉:异常状况在Java中被称做Error(错误)或Exception(异常),是Throwable类的子类,在程序中的缘由是:①动态连接错,如没法找到所需的class文件。②运行时错,如对一个空指针的引用。程序使用了throw语句。
当异常发生时,Java虚拟机采起以下措施:
操做数栈区:机器指令只从操做数栈中取操做数,对它们进行操做,并把结果返回到栈中。选择栈结构的缘由是:在只有少许寄存器或非通用寄存器的机器(如 Intel486)上,也可以高效地模拟虚拟机的行为。操做数栈是32位的。它用于给方法传递参数,并从方法接收结果,也用于支持操做的参数,并保存操做的结果。例如,iadd指令将两个整数相加。相加的两个整数应该是操做数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两个整数将从堆栈弹出、相加,并把结果压回到操做数栈中。
每一个原始数据类型都有专门的指令对它们进行必须的操做。每一个操做数在栈中须要一个存储位置,除了long和double型,它们须要两个位置。操做数只能被适用于其类型的操做符所操做。例如,压入两个int类型的数,若是把它们看成是一个long类型的数则是非法的。在Sun的虚拟机实现中,这个限制由字节码验证器强制实行。可是,有少数操做(操做符dupe和swap),用于对运行时数据区进行操做时是不考虑类型的。
Program counter
Java虚拟机的寄存器用于保存机器的运行状态, 与微处理器中的某些专用寄存器相似。Java虚拟机的寄存器有四种:
在上述体系结构图中,咱们所说的是第一种,即程序计数器,每一个线程一旦被建立就拥有了本身的pc寄存器。当线程执行Java方法的时候,它包含该线程正在被执行的指令的地址。可是若线程执行的是一个本地的方法,那么程序计数器的值就不会被定义。
PC寄存器的内容老是指向下一条将被执行指令的地址,这里的地址能够是一个本地指针,也能够是在方法区中相对应于该方法起始指令的偏移量。
Native method stack
对于一个运行中的Java程序而言,它还可能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就再也不受到虚拟机关于结构和安全限制方面的约束,它既能够访问虚拟机的运行期数据区,也可使用本地处理器以及任何类型的栈。例如,本地栈是一个C语言的栈,那么当C程序调用C函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现Java虚拟机时,本地方法接口使用的是C语言的模型栈,那么它的本地方法栈的调度与使用则彻底与C语言的栈相同。
(这里出现JVM没法控制的内存溢出问题native heap OutOfMemory)