java虚拟机编译器(一)

java虚拟机是为支持java编程语言而设计的。Oracle的JDK软件包括两部分:一部分是将java源代码编译成java虚拟机的指令集的编译器,另一部分是用于实现java虚拟机的运行时环境。

3.1、实例格式说明

如果读者阅读过汇编代码,应该很熟悉实例的格式。所有指令的格式如下: <index><opcode><operand1>[<operand2>……] <index>是指令操作码在数组中的下标,该数组以字节形式来存储当前方法的java虚拟机代码。也可以认为<index>是相对于方法起始处的字节偏移量。<opcode>为指令的操作码的助记符号,<opcodeN>是指令的操作数,一条指令可以有0至多个操作数。<comment>为行尾的注释,比如: 8 bipush 100 //Push int constant 100 注释中的某些部分由javap自动加入,其余部分由作者手动添加。每条指令之前的<index>可以作为控制转移指令(control transfer instruction)的跳转目标。例如,goto 8指令表示跳转到索引为8的指令上继续执行。

3.2、常量、局部变量和结构控制的使用

java虚拟机的代码展示了java虚拟机的设计和类型使用所遵循的一些通用特性。从第一个例子我们就可以感受到许多这类特性,现详解如下。 spin是很简单的方法,它进行了100次空循环:

  void spin(){
•      int i;
•      for(i=0;i<100;i++){
•          ;
•      }
  }

编译后可能产生如下代码:

  0     iconst_0     //push int constant 0
  1      istore_1      //store into local variable 1(i=0)
  2      goto 8         //first time through don't increment
  5      iinc 1 1         //increment local variable 1 by 1 (i++)
  8      iload_1      //push local variable 1 (i)
  9      bipush 100 //push int constant 100
  11     if_icmplt 5 //compare and loop if less than (i<100)
  14    return        //return void when done

java虚拟机是基于栈架构设计的,java虚拟机的大多数操作是从当前栈帧的操作数栈取出一个或多个操作数,或将结果压入操作数栈中。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所需·的操作数栈和局部变量表。每个线程在运行时的任意时刻,都会包含若干个有嵌套的方法调用而产生的栈帧,同时也会包含等量操作数栈,但是只有当前栈帧中的操作数栈才是活动的。 java虚拟机指令集使用不同的字节码来区分不同的操作数类型,以操作数各种类型的数据。在上文的spin方法中,只有针对int类型的运算。因此在编译码里面,对数据类型进行操作的指令,都是针对int类型的。 在spin方法中,0和100两个常量分别使用了两条不同的指令压入操作数栈。对于0采用了iconst_0指令,它属于iconst<i>指令族。而对于100则采用了bipush指令,这个指令会获取它的直接操作数,并将其压入操作数栈中。 java虚拟机经常利用操作码来隐式地宝航某些操作数,例如指令iconst<i>可以压入int常量-1、0、1、2、3、4或5, iconst_0表示把int类型的0值压入操作数栈,这样 iconst_0就不需要专门为入栈操作保持直接操作数的值了,而且也避免了操作数的读取和解析步骤。在本例中,把压入0这个操作的指令有 iconst_0改为bipush 0也能获取正确的结果,但是spin的编译码会因此额外增加1个字节的长度。简单实现的虚拟机可能要在每次循环时消耗更多的时间用于获取和解析这个操作数。因此使用隐式操作数可以让编译后的代码更简洁,更高效。 在spin方法中,int类型的i保存在第一个局部变量中。因为大部分java虚拟机指令操作的都是从栈中弹出的值,而不是局部变量本身,所以在针对java虚拟机所编译的代码中,经常见到局部变量表和操作数栈之间传递值的指令。在指令集里,这类操作也有特殊的支持。spin方法第一个局部变量的传递由istore_1 、iload_1 指令完成,这两个指令都默认是对第一个局部变量进行操作的。istore_1指令的作用是从操作数栈中弹出一个int类型的值,并保持在第一个局部变量中。iload_1指令的作用是将第一个局部变量的值压入操作数栈。 某些频繁处理局部变量的操作在java虚拟机中也有特别的指令来处理 iinc指令的作用是对局部变量加上一个长度为1字节的有符号增量。比如spin方法中的 iinc指令,它的作用是对第一个局部变量(第一个操作数)的值增加1(第二个操作数)。 iinc指令很适合实现循环结构。 spin方法的循环部分由这些指令完成:

  5      iinc 1 1         //increment local variable 1 by 1 (i++)
  8      iload_1      //push local variable 1 (i)
  9      bipush 100 //push int constant 100
  11     if_icmplt 5 //compare and loop if less than (i<100)

bipush指令将int类型的100压入操作数栈,然后 if_icmplt指令将100从操作数栈中弹出并与i进行比较,如果满足条件(即i的值小于100),将转移到索引为5的指令继续执行,开始下一轮的的迭代。否则,程序将执行 if_icmplt的吓一条指令,即return指令

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐