Java虚拟机(八)

执行引擎

Java虚拟机实现的核心是执行引擎。在java虚拟机规范中,执行引擎的行为是以指令集被定义的。对于每条指令,规范详细描述了一个java虚拟机实现在执行字节码的时候遇到这条指令该执行什么样的操作。

跟java虚拟机一样,说到执行引擎也涉及到三个方面的概念:一个抽象规范,一个具体实现,或者一个运行时实例。抽象规范定义了执行引擎对指令集的行为。具体实现,可能使用各种技术,软件,硬件,或者两者相结合。一个执行引擎的运行实例是一个线程。

java程序中的每一个线程都是不同的虚拟机执行引擎实例。从线程生命周期的开始到结束,它一直在执行字节码或者native方法。

指令集

一个方法的字节码流是java虚拟机的一系列指令。每条指令由一个字节的操作码和0个或多个操作数组成。操作码表明了要被执行的操作,操作数提供了java虚拟机执行操作码指定操作所需的额外信息。操作码本身指出了它是否需要操作数,以及操作数的格式。很多java虚拟机指令没有操作数。根据操作码,虚拟机除了使用跟在操作码后面的操作数外,还可能引用存储在其他区域的数据。当虚拟机执行指令时,它可能使用当前常量池中的条目,当前帧本地变量的条目或者位于当前帧顶部的操作数栈的值。

抽象的执行引擎每次执行字节码的一条指令 。这个过程发生在运行在虚拟机程序的每一个线程中。一个执行引擎获取一个操作码,如果这个操作码含有操作数,则获取操作数,执行操作码和操作数请求的动作,然后获取另一个操作码。字节码的执行会持续进行直到线程从开始方法返回或者没有捕获到抛出的异常。

有时,执行引擎可能会遇到一条需要请求native方法调用的指令。在这种情况下,执行引擎会尝试调用native方法,当native方法返回时(正常返回),执行引擎会继续执行字节码流的下一条指令。

native方法可以看作是程序员自定义的对java虚拟机指令集的扩展。如果一条指令请求一个native方法调用,执行引擎会调用这个native方法。运行native方法就是java虚拟机如何执行这条这条指令。当native方法返回时,虚拟机继续执行下一条指令。如果native方法抛出了异常,java虚拟机会按照与其他指令一样的异常处理方式来处理。

决定下一条执行的指令属于执行指令的一部分。一个执行引擎有三种方式可以决定下一条获取的操作码。对于大多数指令,下一条操作码直接跟随在当前操作码和操作数的后面。对于某些指令,例如goto和return,执行引擎以当前指令执行的一部分的方式来决定下一条操作码。如果指令抛出一个异常,执行引擎通过搜寻适当的catch语句决定下一条获取的操作码。

有几条指令可以抛出异常,例如,athrow指令显性地抛出一个异常。这条指令是java源代码throw语句的编译形式。每次athrow被执行的时候,它就会抛出一个异常。其他指令只有在遇到某种条件时才会抛出异常,例如,如果java虚拟机发现程序正尝试用一个整数除0,它会抛出一个Arithmeticxception异常。当执行下面4条指令时可能会出现这种情况——idiv,ldiv,irem和lrem——这些指令都是在int或者long上执行除法或求余操作。

java虚拟机指令集的每种类型的操作码都有一个助记符。在传统的集合语言类型中,java字节码流可以用助记符和操作数的值来表示。下面是个方法字节码流和助记符的例子:
class Act {

    public static void doMathForever() {
        int i = 0;
        for (;;) {
            i += 1;
            i *= 2;
        }
    }
}
doMathForever()方法的字节码流可以被拆分为下面的几个助记符,java虚拟机规范没有定义任何对方法字节码助记符表示的官方语法。
// Bytecode stream: 03 3b 84 00 01 1a 05 68 3b a7 ff f9
// Disassembly:
// Method void doMathForever()
// Left column: offset of instruction from beginning of method
// |   Center column: instruction mnemonic and any operands
// |   |                   Right column: comment
   0   iconst_0           // 03
   1   istore_0           // 3b
   2   iinc 0, 1          // 84 00 01
   5   iload_0            // 1a
   6   iconst_2           // 05
   7   imul               // 68
   8   istore_0           // 3b
   9   goto 2             // a7 ff f9
上面代码中,左边部分表示从方法字节码的起始位置到每条指令开始处的偏移量,中间部分表示指令和操作数,右边部分是注释。

这种表示助记符的方式很像javap程序的输出。javap允许查看任意class文件的方法字节码助记符。注意跳转地址给出了从方法开始处的偏移量。goto指令使得虚拟机跳转到偏移量为2的指令处(iinc),在字节码流中实际的操作数为-7。要执行这条指令,虚拟机把操作数添加到程序计数器的当前内容里,结果得到偏移量为2的iinc指令的地址。为了使得助记符更易于理解,助记符显示的是“goto 2”而不是“goto -7”。

java虚拟机指令集的关注点是操作数栈。值一般会在使用前被push到操作数栈上,虽然java虚拟机没有存储数值的寄存器,每个方法有一组本地变量,指令集把本地变量看作是一组通过索引来引用的寄存器。然而,存储在本地变量的值在使用前必须先添加到操作数栈中。

例如,用一个本地变量除以另一个本地变量,虚拟机必须把两个本地变量的值都push到操作数栈中,执行除法操作,然后把结果存回本地变量中。要移动一个数组元素的值或者对象域到一个本地变量中,虚拟机必须把这些值先push到操作数栈,然后把它们存储到本地变量中。将一个存储在本地变量的值设置给一个数组元素或者对象域,虚拟机需要把本地变量的值push到操作数栈,然后再从操作数栈中pop出,存储到数组元素或者对象域的堆中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值