3.6 接受参数

  如果n个参数传给一个实例的方法,按照约定,它们被接受并放在这个新方法创建的栈帧中的局部变量表里,在局部变量表中的序号从1到n。这些参数按照它们传递过来的顺序存放。例如:

  

int addTwo(int i, int j) {
return i + j;
}

  编译为

Method int addTwo(int,int)
iload_1 // Push value of local variable (i)
iload_2 // Push value of local variable (j)
iadd // Add; leave int result on operand stack
ireturn // Return int result

  按照约定,一个实例的引用要传递到当前实例方法的局部变量0。在java编程语言中这个实例可以通过关键词this来访问。

  类(static)方法不拥有实例,所以对他们来说没有必要使用局部变量0。类方法的参数是的从局部这些变量0开始的。如果addTwo是一个类方法,它的参数传递和前面类似:

static int addTwoStatic(int i, int j) {
return i + j;
}

  编译为:

Method int addTwoStatic(int,int)
iload_0
iload_1
iadd
ireturn

  与前面的唯一不同是参数从局部变量0开始而不是1。

3.7 调用方法

  对普通实例方法调用是在运行时根据对象类型进行分派的(相当于在 C++中所说的“虚方法”)。这样的调用是通过invokevitual指令来实现的,该指令的参数是一个指向运行时常量池条码的索引,索引指向的内容给出了对象的类类型的二进制名称的内部形式、要调用的方法名称以及该方法的描述符(4.3.3)。

  为了调用之前定义的实例方法addTwo,我们可以这么写:

int add12and13() {
return addTwo(12, 13);
}

  编译为:

Method int add12and13()
aload_0 // Push local variable (this)
bipush // Push int constant
bipush // Push int constant
invokevirtual # // Method Example.addtwo(II)I
ireturn // Return int on top of operand stack;
// it is the int result of addTwo()

  方法调用首先需要将当前实例的引用(this)压入操作数栈。这个方法的调用参数,int值12和13,紧接着也被压入操作数栈。当addTwo方法的栈帧被创建时,这些传递到这个方法的参数成为新的栈帧中局部变量中的初始值。也就是说,被调用者压入操作数栈中的this引用和两个参数,将成为被调用方法中局部变量0,1,2的初始值。

  最终,addTwo方法被调用。当它返回时,它的int类型的返回值被压入调用者(add12and13方法)的栈帧的操作数栈中。然后它的返回值会被立即返回给add12and13的调用者。

  add12and13的返回是由ireturn指令来实现的。ireturn指令在当前栈帧的操作数栈中拿到addTwo的int类型返回值,然后压入调用者的栈帧的操作数栈中。ireturn然后将控制权返回给调用者,将调用者的栈帧变成当前帧。对于许多的数值类型和引用类型,java虚拟机提供了对应的返回指令,同样也包括没有返回值的return指令。所有的方法调用的所有变量都使用这一组返回指令。

  invokevirtual指令的操作数(在例子中是运行时常量池索引 #4)不是类实例中方法的偏移量。编译器不知道类实例的内部布局。作为替代,它产生符号引用来指向实例的方法,这些符号引用存储在运行时常量池中。那些运行时常量池中的元素在运行时被解析,从而确定实际的方法位置。其他java虚拟机指令访问类实例也采用相同的方式。

  调用addTwoStatic(一个类中的addTwo的静态变体),也是类似的:

int add12and13() {
return addTwoStatic(12, 13);
}

  尽管使用了一个不同的java虚拟机方法调用指令:

Method int add12and13()
bipush
bipush
invokestatic # // Method Example.addTwoStatic(II)I
ireturn

  编译一个类静态方法的调用和编译一个实例方法的调用是非常相似,除了this没有被调用者传递。因此将要接收到的方法参数从局部变量0开始。invokestatic指令总是用于调用类方法。

  invokespecial指令必须被用于调用实例的初始化方法。它同样用于调用父类的方法(super),以及调用private方法。例如,对于给定的类Near和Far定义如下:

  

class Near {
int it;
public int getItNear() {
return getIt();
}
private int getIt() {
return it;
}
} class Far extends Near {
int getItFar() {
return super.getItNear();
}
}

  Near.getItNear方法(调用了一个private方法),编译为:

Method int getItNear()
aload_0
invokespecial # // Method Near.getIt()I
ireturn

  Far.getItFar(调用了一个父类的方法),编译为:

Method int getItFar()
aload_0
invokespecial # // Method Near.getItNear()I
ireturn

  注意,使用invokespecial指令调用方法总是传递this到被调用的方法作为它的第一个参数。照例使用局部变量0来接收。

  为了调用一个方法,编译器必须产生一个方法描述符,这个方法描述符中记录了实际的参数和返回类型。编译器在方法调用时不会处理参数的类型转换问题,只是简单的将参数压入操作数栈,且不改变其类型。通常,编译器会把方法所在的对象的引用压操作数栈,接着按顺序压入方法参数。编译器生成一个invokevitual指令,它引用了一个描述符,这个描述符描述了参数和返回类型。作为方法解析时的特殊处理过程,一个用于调用java.lang.invoke.MethodHandle的invoke或者invokeExact方法的invokevirtual指令会提供一个方法描述符,这个方法描述符符合语法规则,并且在描述符中确定的类型将会被解析。

3.8 使用类实例

  java虚拟机通过new指令来创建java虚拟机类实例。回想一下,在Java虚拟机级别,构造函数作为方法出现,它使用所编译器提供的名称<init>。这个特殊名字的方法被称为实例初始化方法。多个实例初始化方法,对应于多个构造器,可能存在已一个给定的类中。一旦创建了类实例并且其实例变量(包括该类及其所有超类的实例变量)已初始化为其默认值,就会调用新类实例的实例初始化方法。 例如:

Object create() {
return new Object();
}

  编译为:

Method java.lang.Object create()
new # // Class java.lang.Object
dup
invokespecial # // Method java.lang.Object.<init>()V
areturn

  类实例的传递和返回(通过引用类型)和数值类型非常相似,尽管引用类型有其完备的指令,例如:

int i;                                  // An instance variable
MyObj example() {
MyObj o = new MyObj();
return silly(o);
}
MyObj silly(MyObj o) {
if (o != null) {
return o;
} else {
return o;
}
}

  编译为:

Method MyObj example()
new # // Class MyObj
dup
invokespecial # // Method MyObj.<init>()V
astore_1
aload_0
aload_1
invokevirtual # // Method Example.silly(LMyObj;)LMyObj;
areturn Method MyObj silly(MyObj)
aload_1
ifnull
aload_1
areturn
aload_1
areturn

  类实例的字段(实例变量)通过getfield和putfield指令来访问。假设i是一个int类型的实例变量,它的setIt

和getIt定义如下:

  

void setIt(int value) {
i = value;
}
int getIt() {
return i;
}

  编译为:

Method void setIt(int)
aload_0
iload_1
putfield # // Field Example.i I
return Method int getIt()
aload_0
getfield # // Field Example.i I
ireturn

  和方法调用指令的操作数一样,putfield和getfield指令的操作数(运行时常量池索引#4)不是类实例字段的偏移量。编译器产生这个实例字段的符号引用,存储在运行时常量池。这些运行时常量池中的元素在运行时被解析从而决定引用对象的字段的位置。

3.9 数组

  java虚拟机的数组同样是对象。数组的创建和操作使用不同的指令集。newarray指令用于创建一个数值类型的数组。代码如下:

void createBuffer() {
int buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}

  可能编译成:

Method void createBuffer()
bipush // Push int constant (bufsz)
istore_2 // Store bufsz in local variable
bipush // Push int constant (value)
istore_3 // Store value in local variable
iload_2 // Push bufsz...
newarray int // ...and create new int array of that length
astore_1 // Store new array in buffer
aload_1 // Push buffer
bipush // Push int constant
iload_3 // Push value
iastore // Store value at buffer[]
aload_1 // Push buffer
bipush // Push int constant
iaload // Push value at buffer[]...
istore_3 // ...and store it in value
return

  anewarray指令用于创建一维对象引用数组,例如:

void createThreadArray() {
Thread threads[];
int count = 10;
threads = new Thread[count];
threads[0] = new Thread();
}

  编译为:

Method void createThreadArray()
bipush // Push int constant
istore_2 // Initialize count to that
iload_2 // Push count, used by anewarray
anewarray class # // Create new array of class Thread
astore_1 // Store new array in threads
aload_1 // Push value of threads
iconst_0 // Push int constant
new # // Create instance of class Thread
dup // Make duplicate reference...
invokespecial # // ...for Thread's constructor
// Method java.lang.Thread.<init>()V
17 aastore // Store new Thread in array at 0
18 return

  anewarray指令同样可以用于创建多维数组的第一维。multianewarray指令可以用于一次创建多维数组。例如,三维数组:

int[][][] create3DArray() {
int grid[][][];
grid = new int[10][5][];
return grid;
}

  这样被创建:

Method int create3DArray()[][][]
bipush // Push int (dimension one)
iconst_5 // Push int (dimension two)
multianewarray # dim # // Class [[[I, a three-dimensional
// int array; only create the
// first two dimensions
astore_1 // Store new array...
aload_1 // ...then prepare to return it
areturn

  multianewarray指令的第一个操作数是运行时常量池中的索引,这个索引指向被创建的数组类型。第二个操作数表示实际创建出来的数据具有的维数。multianewarray指令可用于创建该类型的所有维度,例如create3DArray代码中的那样。注意多维数组也仅仅是一个对象,所以加载和返回使用了aload_1指令和areturn指令。更多关数组类名的信息,参考4.4.1.

  所有的数组都有长度,可以通过arraylength指令来过去。

3.10 编译switch

  switch语句的编译使用tableswitch和lookupswitch指令。当switch的case可以有效地表示为目标偏移表中的索引时,使用tableswitch指令。如果switch表达式的值超出了有效索引的返回,则使用default的值。例如:

int chooseNear(int i) {
switch (i) {
case 0: return 0;
case 1: return 1;
case 2: return 2;
default: return -1;
}
}

  编译为:

Method int chooseNear(int)
iload_1 // Push local variable (argument i)
tableswitch to : // Valid indices are through
: // If i is , continue at
: // If i is , continue at
: // If i is , continue at
default: // Otherwise, continue at
iconst_0 // i was ; push int constant 0...
ireturn // ...and return it
iconst_1 // i was ; push int constant 1...
ireturn // ...and return it
iconst_2 // i was ; push int constant 2...
ireturn // ...and return it
iconst_m1 // otherwise push int constant -...
ireturn // ...and return it

  java虚拟机的tableswtich和lookupswitch指令只操作int类型的数据。因为对于byte,char或者short的操作会内部提升到int,switch表达式中使用了这些类型将会编译为int类型。如果chooseNear方法使用short类型来写,将会使用int类型来生成相同的java虚拟机指令。使用switch时,其他的数值类型必须窄化为int类型。

  如果switch的case比较分散,使用tableswitch指令来表示将会在空间上低效。可以使用lookupswitch指令来代替。lookupswitch指令将int键(case标签的值)与表中的目标偏移量配对。当lookupswitch指令运行时,switch表达式的值将会和表中的key进行比较。如果其中一个key和switch表达式的值匹配上,将从其关联的目标便宜位置继续执行。如果没有匹配上,将从default处继续执行。例如,下面的代码:

int chooseFar(int i) {
switch (i) {
case -100: return -1;
case 0: return 0;
case 100: return 1;
default: return -1;
}
}

  看起来和chooseNear很像,除了lookupswitch指令:

Method int chooseFar(int)
iload_1
lookupswitch :
-:
:
:
default:
iconst_m1
ireturn
iconst_0
ireturn
iconst_1
ireturn
iconst_m1
ireturn

  Java虚拟机指定lookupswitch指令的表必须按键排序,以便实现可以使用比线性扫描更高效的搜索。即便如此,lookupswitch指令必须搜索其键以进行匹配,而不是简单地执行边界检查并索引到像tableswitch这样的表。因此,当空间条件允许时,tableswitch指令可能比lookupswitch指令更高效。

3.11 操作数栈上的操作

  java虚拟机拥有大量的指令可以将操作数栈上的内容当做无类型数据进行操作。这是非常有用的,因为java虚拟机依赖于其对操作数栈的灵活操作。例如:

public long nextIndex() {
return index++;
} private long index = 0;

  编译为:

Method long nextIndex()
aload_0 // Push this
dup // Make a copy of it
getfield # // One of the copies of this is consumed
// pushing long field index,
// above the original this
dup2_x1 // The long on top of the operand stack is
// inserted into the operand stack below the
// original this
lconst_1 // Push long constant
ladd // The index value is incremented...
putfield # // ...and the result stored in the field
lreturn // The original value of index is on top of
// the operand stack, ready to be returned

  注意,java虚拟机决不允许操作数栈的操作指令去修改或者破坏操作数栈上周一个独立的值。

最新文章

  1. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级
  2. 在CentOS或RHEL上安装Nux Dextop仓库
  3. linux VI search command 搜索 加入行号
  4. linux(ubuntu)安装时遇到的问题
  5. DOS下导入dmp文件到Oracle数据库
  6. fastxml Jackson JsonNode (ObjectNode) 转 List
  7. Window Phone 8 应用程序连接扩展图片中心,图片扩展,图片查看器
  8. webpack学习(一)
  9. Codeforces785E - Anton and Permutation
  10. css设置文字上下居中,一行文字居中,两行或多行文字同样居中。
  11. 关于Android开发中Arm、X86和Mips(草稿)
  12. Canvas入门到高级详解(中)
  13. Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动【转】
  14. discuz的学习和部署
  15. linux shell 脚本攻略学习12--文件权限详解,chmod命令详解,chown命令详解,chattr命令详解
  16. Dynamics 365 Customer Engagement 中对API的调整内容分享
  17. shared_ptr&amp;scoped_ptr&amp;weak_ptr
  18. You need to use a Theme.AppCompat theme (or descendant) with this activity问题
  19. 对EJB的认识
  20. ViewPager的使用方法

热门文章

  1. vector中数据释放崩溃问题
  2. Kvm --05 密码保护:Kvm管理之WebVirtMgr
  3. ERROR- 开发常见error
  4. quickbi报错AE0510000002
  5. Swagger添加文件上传测试
  6. vuex的配置使用
  7. Es学习第十一课,使用java操作elasticsearch
  8. 线程中的sleep()、join()、yield()方法有什么区别?
  9. Heartbeat安装及配置
  10. 【HDOJ6606】Distribution of books(二分,BIT)