为JAVA虚拟机编译(第1部分)

本章只涉及到从使用Java语言编写的源代码编译为Java虚拟机指令集的编译器。

3.1 示例的格式说明

本章节的示例主要包括有源文件和Java虚拟机代码注解列表(Annotated Listings),其中,Java虚拟机的代码注解列表是由Oracle的1.0.2版本的JDK的javac编译器生成。

Java虚拟机代码将使用Oracle的javap工具所生成的非正式的“虚拟机汇编语言“格式来描述。

<index> <opcode> [<operand1> [<operand2>...]] [<comment>]

是code[]数组中的指令的操作码的索引,也可以认为是相对于方法起始处的字节偏移量 。为指令的操作码的助记符号,是指令的操作数,一条指令可以有0至多个操作数。为行尾的语法注释,譬如:

8 bipush 100 // Push int constant 100

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

spin()是一个很简单的方法,它进行了100次空循环

void spin() { 
    int i; 
    for (i = 0; i < 100; i++) { 
        ; // Loop body is empty 
    } 
}

编译后代码如下:

Method void spin() 
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虚拟机是基于栈架构设计的,它的大多数操作是从当前栈帧的操作数栈取出1个或多个操作数,或将结果压入操作数栈中。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所需的操作数栈和局部变量表。每条线程在运行时的任意时刻,都会包含若干个由不同方法嵌套调用而产生的栈帧,当然也包括了若干个栈帧内部的操作数栈,但是只有当前栈帧中的操作数栈才是活动的。

istore_1指令作用是从操作数栈中弹出一个int型的值,并保存在第一个局部变量中。

iload_1指令作用是将第一个局部变量的值压入操作数栈。

iinc指令的作用是对局部变量加上一个长度为1字节有符号的递增量。

if_icmplt指令将100从操作数栈中弹出值并与i进行比较,如果满足条件(即i的值小于 100),将转移到索引为5的指令继续执行

double类型的值占用两个局部变量的空间。譬如下面例子展示了double类型值的访问:

double doubleLocals(double d1, double d2) { 
    return d1 + d2; 
}

编译后代码如下:

Method double doubleLocals(double,double) 
0 dload_1 // First argument in local variables 1 and 2 
1 dload_3 // Second argument in local variables 3 and 4 
2 dadd 
3 dreturn

注意:局部变量表中使用了一对局部变量来存储doubleLocals()方法中的double值,这对局部变量不能被分开来进行单个操作。

spin()方法的for循环语句中,对于int型值的判断可以统一用if_icmplt指令实现;但是,在Java虚拟机指令集合中,对于double类型的值的没有这样的指令。如果把spin方法变量改为double,必须在dcmpg指令后面再联合iflt指令来实现。

如果把spin方法变量改为double型,编译后代码如下:

Method void spin() 
0 dconst_0 // Push double constant 0.0 
1 dstore_1 // Store into local variables 1 and 2 
2 goto 9 // First time through don’t increment 
5 dload_1 // Push local variables 1 and 2 
6 dconst_1 // Push double constant 1.0 
7 dadd // Add; there is no dinc instruction 
8 dstore_1 // Store result in local variables 1 and 2 
9 dload_1 // Push local variables 1 and 2 
10 ldc2_w #4 // Push double constant 100.0 
13 dcmpg // There is no if_dcmplt instruction 
14 iflt 5 // Compare and loop if less than (i < 100.0)
17 return // Return void when done

如果把变量改为short型,编译后的代码:

Method void spin() 
0 iconst_0 
1 istore_1 
2 goto 10 
5 iload_1 // The short is treated as though an int 
6 iconst_1 
7 iadd 
8 i2s // Truncate int to short 
9 istore_1 
10 iload_1 
11 bipush 100 
13 if_icmplt 5 
16 return

Java虚拟机支持下,对int类型的数据的大部分操作可以直接进行。这在一定程度上是考虑到了Java虚拟机操作数栈和局部变量表的实现效率。当然也有考虑到了大多数程序都会对int型数据进行频繁操作的原因。

在Java虚拟机中,缺乏对byte、char和short类型数据直接操作的支持所带来的问题并不大,因为这些类型的值都在编译过程中就自动被转换为int类型

Java虚拟机对于long和浮点类型(float和double)提供了中等程度的支持,比起int类型数据所支持的操作,它们仅缺少了条件转移指令部分,其他操作都与int类型具有相同程度的支持。

说明