寄存器

  Java虚拟机的寄存器和我们计算机中的计算机是相似的。然而,由于虚拟机试基于栈的,它的寄存器不是用于传递和接收参数。在Java中,寄存器保存机器的状态以及在每行字节码执行执行后进行状态更新,保持状态。下面的四个寄存器保存了虚拟机的状态:

  ● 框架指针寄存器(frame):包含指向当前方法执行环境的指针。

  ● 操作数栈顶指针寄存器(optop):包含了指向操作对数栈的栈顶指针, 用于算数表达式求值。

  ● 程序计数器寄存器(pc):包含了下一个要被执行的字节码的地址。

  ● 变量寄存器(vars):包含了指向局部变量的指针。

  这些寄存器都是32位的,且能立即分配。这也许是因为编译器需要知道局部变量的大小和操作数栈,以及翻译器需要知道执行环境的大小。

  栈

  Java虚拟机用操作数栈为方法和操作提供参数,然后将结果返回。所有的字节码指令从栈中获取操作数,在操作数上进行操作(运算),并且将结果返回到栈中。和虚拟机的寄存器一样,操作数栈的位宽也是32位。

  操作数栈遵循后进先出的原则,并且要求栈中的操作数有一个特定的顺序。例如:字节码指令isub需要在栈顶存放两个整数,这也意味着以前指令集的操作必须在栈中压入了两个整数。isub指令退出栈顶的两个操作数,对他们进行减法运算,然后将运算结果压入栈中。

  在Java中,整型是一种原始数据类型。每种原始数据类型都有特定的指令来对该类型的数据进行操作(运算)。例如:lsub指令用来执行长整型的减法,fsub用来执行浮点数的减法,dsub用来执行双精度浮点数的减法。真是因为这样,将两个整型数据放在栈顶,然后把他们当做一个长整型数据时非法的。然后将一个64位长度的长整型数据放入栈中,它在栈中占用两个32位的位置。

  我们Java程序中的每一个方法都有与之相对应的堆框架(stack frame),堆框架保存方法状态需要三中类型的数据:方法的局部变量、方法的执行环境以及方法的操作数栈。尽管局部变量区和执行环境数据集的大小一般是在方法调用之前分配了,操作数栈的大小随着方法的字节码指令的执行而改变。由于Java栈是32位的,64位的操作数不能保证64位对齐。

  执行环境

  执行环境作为一个数据集保存在栈中,它用来处理动态链接、正常方法返回和异常的产生。为了处理动态链接,执行环境包含方法的符号引用、当前方法和当前类的变量。这些符号调用会通过动态链接到符号表翻译成实际的方法调用。

  每当一个方法正常完成的时候,调用方法将获得一个返回值。执行环境处理正常的方法返回时通过恢复调用者的寄存器和增加调用者的程序计数器值以跳过方法执行的指令来实现的。

  如果当前方法的运行正常完成,调用方法将会获得一个返回值。调用方法执行一个具有正确返回值的返回指令时完成这个操作。

  如果调用方法执行一个具有不正确返回值类型的返回指令时,方法会抛出一个异常或错误。异常可能发生在动态链接失败(如:无法找到类文件),或者运行时错误(如:数组引用超出数组范围)。当错误发生时,执行环境生成一个异常。

  垃圾回收堆

  每个运行在Java运行时环境的程序都会有一个垃圾回收堆分配给它。由于类的实例对象都是从堆里分配,这个堆也叫做内存分配池。在大多数系统中,堆的大小被默认设置为1MB。

  尽管堆的大小在我们启动程序的时候设定了,但它可以扩大,例如:当一个新对象被分配是,为了保证堆不会变的过大,那些不在使用的对象将会自动销毁或者由Java虚拟机进行垃圾回收。

  Java后台线程自动执行垃圾回收,每个在Java运行时环境中运行的线程用于与之相关的两个栈:第一个栈用于Java代码,第二个栈用与垃圾收集代码(C code)。这些栈所用的内存从总系统内存池中获取。每当一个线程开始执行,它被分配一个大栈用于Java代码和垃圾收集代码。在大多数系统中为Java代码分配的大栈空间默认是400KB,为垃圾收集代码分配的大栈空间默认是128KB。

  如果我们系统用内存限制,我们可以强制Java执行更激进的清理操作从而减少总的内存使用量。这个通过缩减Java代码和垃圾收集代码的大空间来实现。如果我们的系统拥有大量的内存,我们可以迫使Java执行更少侵略性的清理,因此减少了后台处理的数量。这个通过增加Java代码和垃圾收集代码的大空间来实现。