概述
在编译后class内部,method的代码以字节码指令序列的方式存储。为了生成和转换class,最基本的是知道这些指令、了解它们如何工作。
Execution model
Java代码在线程内被执行。每个线程有自己的执行栈,它由frame组成。每个frame代表了一个方法调用:每个时间一个方法被调用,新frame被压入当前线程的执行栈,当方法正常返回或有异常,这个frame被弹出栈且继续执行。每个frame有两部分:本地变量和操作栈 。本地变量可以通过index随机访问,操作栈被字节码指令操作。即意味着此栈中的值只能以LIFO顺序访问。
本地变量和操作栈的大小依赖于method code。它在编译时间就被计算且随字节码指令一起存春在class中。所有frame的大小都一样,但不同方法对应的frame的本地变量和操作栈的大小不一样。
上图展示了一个执行栈有三个frame,第一个frame有3个本地变量,它的操作栈大小为4,有量个值。当一个frame被创建后,它被初始化为空操作栈,它的本地变量初始化为目标对象this,和方法参数。例如调用a.equals(b),创建一个空操作栈,前两个本地变量初始为a和b,其他的本地变量没有初始化。
在本地变量和操作栈中的slot可以容纳任何java值,除了long和double值之外。这些值需要两个slot。例如调用Math.max(1L,2L),创建一个frame,1L占用第一、第二个slot,2L占用第三、第四个slot。
Bytecode instructions
一个字节码指定由操作码和固定数值的参数组成。1) opcode:无符号byte。字节码名称,通过记忆性字符标记。例如opcode值0用NOP来标记,表示指定不做任何事情。
2)arguments:定义指定精确行为的静态值。它们在opcode之后给出。例如GOTO label指令,它的opcode值是167,接受一个参数lable。lable表示下一个指令要执行的地方。指令参数和指定操作数不能混淆。参数值是静态的,存储在编译好的代码中,而操作值来自于操作栈,它们仅在运行时才直到。
字节码指令可以被分为两类:一小类,从本地变量到操作栈的值传输。另外一类指令仅在操作栈上执行,它们从栈中弹出值,计算结果,压回栈。ILOAD,LLOAD,FLOAD,ALOAD指令读取本地变量,将值压入操作栈。它们接受本地变量的Index作为参数。ILOAD用来加载boolean,byte、char、short、int本地变量。LLOAD,FLOAD和DLOAD各自用来加载long、float、double值(LLOAD和DLOAD实际上加载i和i+1两个slot)。ALOAD用来加载非基本类型值,如object数组引用。类似ISTORE,LSTORE,FSTORE,DSTORE和ASTORE指令从操作栈弹出值并存储到本地变量index中。
xload和xstore指令都是有类型的,确保没有非法的转换。在方法执行过程中存储在本地变量中的值能发生变化。所有其他字节码指令仅在操作栈上工作。它们可以被归为以下几类:
stack:这些指令用来操作操作栈上的值。POP,栈顶元素出栈。DUP,压入栈顶元素的拷贝。SWAP,出栈2元素,且以相反的顺序入栈。
Constant:将常量值压入到栈顶。ACONST_NULL压入null,ICONST_0压入0,FCONST_0压入0f,DCONST_0压入0d。BIPUSH b将字节值b压入栈。SIPUSH s 将short类型 s压入栈。LDC压入二进制int、float、long、double,string或class常量。
Arithmetic and logic(算术和逻辑运算):这些指令将数值从操作栈出栈,接着运算并见结果压入栈。它们不包含任何参数。xADD,xSUB,xMUL,xDIV和xREM对应响应的+,-,*,/,%运算,这里的x要么是I,L,F,D。类似,这里有其他指令对应<<,>>,>>>,|,&和^。
Casts:该指令从栈弹出值,转换为另外一个类型,并将结果压入栈。它对应Java中的转换表达式。I2F,F2D,L2D。CHECKCAST t转换一个引用到另一个类型t。
Object:该指令用来创建对象、锁定它们、测试它们的类型。例如 NEW type指令将一个新的对象压入栈
Fields:该指令读取或写filed的值。GETFIELD owner name desc 指令从栈中弹出对象引用,并压入name field的值。PUTFIELD owner name desc弹出一个值和对象引用,储存值到name域上。上述例子中,object必须是 owner类型,它的filed必须是desc类型。GETSTATIC和PUTSTATIC针对静态域。
Method:这些指令调用方法或构造器。当方法有参数时,它们pop多个value,另外,一个值为target对象,且将方法调用的返回值压入栈。INVOKEVIRTUAL owner name desc调用方法owner类中方法名为name的方法,其中整个方法描述为desc。INVOKESTATIC用来调用静态方法。INVOKESPECIAL调用私有方法和是有构造器。INVOKEINTERFACE调用接口中定义的方法。
Arrays:该指令用来读取和写如数组中的值。xALOAD指令pop一个index和一个数组并且将index处的array值压入栈。xASTORE指令pop一个值、index和value和数组,并且在数组中index位置存储这个值。x可以是I,L,F,D,A也可以是B,C和S。
Jumps:如果某条件为真,该指令跳转到二进制指令。它们用于编译if,for,do,while,break和continue指令。例如,IFEQ label pop一个int,如果value为0,跳转到指令lable。还有许多其他jump指令,如IFNE,IFGE。TABLESWITCH和lookupswitch对应Java的switch指令。
Return:xReturn和Return指令用来终止方法执行且返回结果到调用者。Return对应void,xReturn对应其他方法返回类型。
Examples
package pkg;
public class Bean {
private int f;
public int getF() {
return this.f;
}
public void setF(int f) {
this.f = f;
}
}
get方法的字节码是:
ALOAD 0
GETFIELD pkg/Bean f I
IRETURN
第一个指令读取index为0本地变量,该值在get方法调用创建frame时初始化给this对象,且将这个值压入操作栈。第二个指令从栈弹出值(例如this),且压入该对象的f 数据域
set方法的字节码:
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
RETURN
第一个指令将第一个本地变量this压入操作栈。第二指令将index=1本地变量压入栈,该变量在set方法的frame被创建时,使用参数f的值初始化。第三个指令弹出这些值,且在this对象的f field上存储int值。最后一条指令,销毁当前执行frame,且返回给调用者。
bean class幽默人的public构造函数:
Bean(){
super();
}
构造函数字节码:
ALOAD 0
INVOKESPECIAL java/lang/Object <init> ()V
RETURN
第一条指令将index=0本地变量this压入操作栈。第二条指令弹出值,调用Object的<init>方法。第三条指令返回调用者
更复杂的set方法:
public void checkAndSetF(int f) {
if (f >= 0) {
this.f = f;
} else {
throw new IllegalArgumentException();
}
}
对应的字节码:
ILOAD 1
IFLT label
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
GOTO end
label:
NEW java/lang/IllegalArgumentException
DUP
INVOKESPECIAL java/lang/IllegalArgumentException <init> ()V
ATHROW
end:
RETURN
第一条指令将index=1的本地变量入栈,初始化到f。IFLT指令将值从栈中弹出,并且将它与0比较,如果less than(LT)0,跳转到Lable label。否则do nothing且执行下一条指令。紧接着三条指令与setF方法中的指令相同。GOTO指令无条件跳转到END lable。lable与end之间的指令创建异常并抛出异常。NEW指令创建异常对象并压入操作栈。DUP指令复制在栈顶的值。INVOKESPECIAL指令弹出两分拷贝中的一个,并它的异常构造函数。最后athrow指令弹出剩下的拷贝,抛出异常。
最后
以上就是调皮面包为你收集整理的Core API之Method结构Execution modelBytecode instructionsExamples的全部内容,希望文章能够帮你解决Core API之Method结构Execution modelBytecode instructionsExamples所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复