java虚拟机规范(se8)——java虚拟机结构(六)

2.11 指令集简介

  java虚拟机指令由一个字节的操作码,后面跟着多个0个或多个操作数组成,操作码描述了执行的操作,操作数提供了操作所需的参数或者数据。许多指令没有操作数只包含一个操作码。

  如果忽略异常处理,那java虚拟机使用下面的伪代码循环即可有效工作:

1 do{
2     自动计算pc然后获取pc中的操作码;
3     if (存在操作数)
4         获取操作数;
5     执行这个操作码定义的操作
6 }while(是否有更多需要执行);

    操作数的数量和大小都有操作码决定。如果一个操作数大于一个字节,那么它将以大端顺序存储——高位在前。例如一个16位无符号整数使用两个无符号字节存储,byte1和byte2,它的值位(byte1<<8)|byte2.

  字节码指令流是单字节对齐的,除了lookupswitch和tableswitch指令。由于它们的操作数比较特殊,都是4字节为界划分的,所以这两条指令也需要预留响应的空位来实现对齐。

  

2.11.1 类型和java虚拟机

  java虚拟机指令集中的大多数指令都包含了其操作数的类型信息。例如,iload指令加载一个局部变量到操作数栈中,这个局部变量必须是int类型。fload指令对float值执行相同的操作。这两个指令可以具有相同的实现,但是不能使用相同的操作码。

  对于大多数类型相关的指令,指令类型在操作码助记符中用一个字母显示的表示:i表示int,l表示long,s表示short,b表示byte,c表示char,f表示float,d表示double,a表示reference。一些指令的类型是不确定的,这些指令的助记符中不包含类型字母。例如,arraylength总是操作数组对象。一些指令,例如goto是一个无条件控制转移指令,不操作一个有类型的操作数。

    由于java虚拟机的操作码长度只有一个字节,所以包含了数据类型的操作码对指令集的设计带来很大的压力。如果每个类型指令都支持所有的java虚拟机运行时数据类型,那么只用一个字节表示所有的指令就不太可能了。相应的,java虚拟机的指令集对于特定的操作提供了有限的类型支持,换句话说,不是每种类型的每个操作都有对应的类型指令。根据需要,可以使用单独的指令在不支持和支持的数据类型之间进行转换。

  下表汇总了java虚拟机的指令集支持的类型。具体的指令具有类型信息,通过将opcode列的指令模板中的字母T替换为对应类型列的字母来得到。如果一些指令模板对应的类型列是空的,就表示不存在支持这个类型操作的指令。例如,存在加载int类型的指令iload,但是没有加载byte的指令。

  注意到表中的大部分指令都没有整数类型byte,char和short的形式,也没有一个有boolean类型的形式。编译器在编译期和运行期将byte和shot类型带符号位扩展为int类型,将boolean和char类型进行零扩展为int类型,从而使用int类型指令进行操作。同样的,使用java虚拟机指令处理boolean,byte,char,short类型的数组时也会转换为int类型。因此,大多数对于boolean,byte,short和char类型的操作,实际上都是作为int类型作为运算(computational )类型。

 

opcodebyteshortintlongfloatdoublecharreference
Tipush bipush sipush            
Tconst     iconst lconst fconst dconst   aconst
Tload     iload lload fload dload   aload
Tstore     istore lstore fstore dstore   astore
Tinc     iinc          
Taload baload saload iaload laload faload daload caload aaload
Tastore bastore sastore iastore lastore fastore dastore castore aastore
Tadd     iadd ladd fadd dadd    
Tsub     isub lsub fsub dsub    
Tmul     imul lmul fmul dmul    
Tdiv     idiv ldiv fdiv ddiv    
Trem     irem lrem frem drem    
Tneg     ineg lneg fneg dneg    
Tshl     ishl lshl        
Tshr     ishr lshr        
Tushr     iushr lushr        
Tand     iand land        
Tor     ior lor        
Txor     ixor lxor        
i2T i2b i2s   i2l i2f i2d    
l2T     l2i   l2f l2d    
f2T     f2i f2l   f2d    
d2T     d2i d2l d2f      
Tcmp       lcmp        
Tcmpl         fcmpl dcmpl    
Tcmpg         fcmpg dcmpg    
if_TcmpOP     if_icmpOP         if_acmpOP
Treturn     ireturn lreturn freturn dreturn   areturn

 

java虚拟机实际类型和java虚拟机运算类型之间的映射见下表。

  一些java虚拟机指令如pop和swap在操作数栈上操作时忽略类型,不过这些指令也必须受到运算类型分类的限制,这些分类也在下表中给出了。

实际类型计算类型分类
boolean int 1
byte int 1
char int 1
short int 1
int int 1
float float 1
reference reference 1
returnAddress returnAddress 1
long long 2
double double 2

 

2.11.2 加载和存储指令

  加载和存储指令将值在java虚拟机栈帧的局部变量表和操作数栈之间进行转移。

  •   加载一个局部变量到操作数栈:iload, iload_<n>, lload, lload_<n>, fload, fload_<n>, dload, dload_<n>, aload, aload_<n>
  •   从操作数栈中存储一个值到局部变量表:istore, istore_<n>, lstore, lstore_<n>, fstore, fstore_<n>, dstore, dstore_<n>, astore, astore_<n>
  •   加载一个常量到操作数栈:bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>.
  •   扩充局部变量表的访问索引的指令:wide

  访问对象的属性或者数组的元素也需要和操作数栈进行数据转移。

  上面列出的指令助记符中,有一部分是以尖括号结尾的(例如,iload_<n>),这些指令助记符实际上是代表了一组指令(例如iload_<n>有iload_0,iload_1,iload_2,iload3组成)。这几组指令都是某个带有一个操作数的通用指令(如iload)的特殊形式,它们的操作数是隐含的,不需要存储和获取。除此之外,它们的语义和原生通用指令完全一致(如iload_0的语义和操作数为0的iload指令语义完全一致)。尖括号之间的字母指定该指令族的隐式操作数的类型:对于<n>,表示非负整数; 对于<i>,表示int; 对于<l>,表示long; 对于<f>,表示float; 对于<d>,表示double。 在许多情况下,类型int的形式用于对byte,char和short类型的值执行操作。

  指令族的概念贯穿这个规范。

2.11.3 运算指令

  算数指令将操作数栈上的两个元素进行计算,然后将结果压入操作数栈。主要有两种类型的运算指令,操作整数和操作浮点数。无论哪种类型的运算指令都使用java虚拟机的数字类型。对于byte,short,char以及boolean类型的值没有直接的整型指令支持它们;这些运算操作使用int类型的指令来操作。整型和浮点型指令在溢出和除零行为上同样不同。运算指令如下:

  •   加:iadd, ladd, fadd, dadd.
  •   减:isub, lsub, fsub, dsub
  •   乘:imul, lmul, fmul, dmul
  •   除:idiv, ldiv, fdiv, ddiv
  •   求余:irem, lrem, frem, drem
  •   取反:ineg, lneg, fneg, dneg
  •   位移:ishl, ishr, iushr, lshl, lshr, lushr
  •   位或:ior, lor
  •   位与:iand, land
  •   位异或:ixor, lxor
  •   局部变量自增:iinc
  •   比较:dcmpg, dcmpl, fcmpg, fcmpl, lcmp

  java虚拟机指令集的语义直接支持java编程语言对于整型和浮点型值操作的语义。

  java虚拟机没有明确表示操作整数数值时的溢出情况。整型操作能抛出异常的指令只有整数除指令(idiv,ldiv)和整数求余指令(irem和lrem),当它们除0时,将抛出ArithmeticException

2.11.4 类型转换指令

  类型转换指令允许java虚拟机中的数字类型相互转换。这些转换操作一般用于实现用户代码的显示类型转换操作,或者用来处理Java虚拟机字节码指令集中指令非完全独立的问题。

 java虚拟机直接支持以下数值的宽化类型转换:

  •   int到long,float或者double
  •   long到float或者double
  •   float到double

  宽化数值转化指令有i2l,i2f,i2d,l2f,l2d以及f2d。这些操作码的助记符直接从名称上给出了类型转换的内容,使用2来作为双关语表示to。例如,i2d指令表示将int值转换为double值。

注意到宽化数值类型转化不包括将整型类型byte,char,short转化为int。如2.11.1中所述,byte,char和short类型的值在内部扩展为int类型,这使得这些转换是隐式的。

  java虚拟机也直接支持下面的窄化数值转化(narrowing numeric conversions):

  •   int 到 byte,short 或者 char
  •   long 到 int
  •   float 到 int 或者 long
  •   double 到 int,long 或者 float

  上面的窄化数值转换是:i2b,i2c,i2s,l2i,f2l,d2i,d2l和d2f。窄化数值转换可能导致结果具有不同的符号,不同的数量级,或者两者都有;转换过程可能导致数值丢失精度。

  将int或者long窄化数值转换为整型T,只是简单的丢弃除最低n位之外的所有位,n指类型T的长度。这回导致结果和输入具有不同的符号位。

  当将浮点型数值窄化转换为类型T时,T为int或者long,遵循以下规则:

  •   如果浮点数为NaN,那么转换结果为int 0或者long 0。
  •   否则,如果浮点型数值不是无穷大,那么浮点数将使用IEEE 754的舍入到0模式转换为整数V,并且下面符合下面两种情况:
    •     如果T是long类型,并且这个转换的整数值V在long的范围之内,那么结果就是V。
    •     如果T是int类型,并且整数值V在int范围之内,那么结果就是V。
  •   否则:
    •     如果数值非常小(比如一个具有非常大数量级的负数或者负无穷大),那么将用int或者long中的最小的数来表示。
    •     如果数值太大(比如一个具有非常大数量级的正数或者正无穷大),那么结果将用int或者long中最大的数来表示。

  将double窄化数值转换为float过程和IEEE 754中一致。结果使用IEEE 754的舍入到零模式转换。如果数值太小无法表示为float,那么使用float的正负0来表示;如果数值太大,那么使用float的正负无穷大来表示。double的NaN总是转换为float的NaN。

 

2.11.5 创建和操作对象

  尽管类实例和数组都是对象,但是java虚拟机创建和操作类实例和数组使用不用指令集:

  •   创建一个类实例:new
  •   创建一个数组:newarray,anewarray,multianewarray。
  •   访问类字段(static fields)和类实例的字段(非静态域):getstatic,putstatic,getfield,putfield。
  •   加载数组元素到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload。
  •   从操作数栈中存储一个元素到数组中:bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore
  •   获取数组的长度:arraylength
  •   检测类实例或者数组的属性:instanceof,checkcast

 

2.11.6 操作数栈管理指令

  提供了一系列指令去直接操作操作数栈:pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap

 

2.11.7 控制转移指令

  控制转移指令可以让java虚拟机有条件或者无条件的跳转到指定指令处执行,而不是继续执行下一条指令。控制转移指令包括有:

  条件分支:ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmple, if_icmpgt if_icmpge, if_acmpeq, if_acmpne

  组合条件分支:tableswitch, lookupswitch

  无条件分支:goto, goto_w, jsr, jsr_w, ret

 

2.11.8 方法调用和返回指令

  下面五个指令用于方法调用:

  invokevitual调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是java语言中最常见的方法分派方式。

  invokeinterface调用接口的方法,它会在运行时搜索一个实现了这个接口方法的对象,找出合适的方法进行调用。

  invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(2.9),私有方法或者父类的方法。

  invokestatic指令用于调用一个类的静态方法。

  invokedynamic指令用于调用的方法是绑定到invokedynamic指令的调用端对象的目标。Java虚拟机将调用端对象绑定到invokedynamic指令的特定词法,这是在第一次执行该指令之前运行引导方法的结果。因此,与调用方法的其他指令不同,invokedynamic指令的每次出现都有一个惟一的链接状态。

  方法的返回指令通过返回类型来区分,包括:ireturn (用于返回类型是 boolean, byte, char, short,或者 int), lreturn, freturn, dreturn, and areturny以及return指令用于返回值是void的方法,实例初始化方法,类或者接口初始化方法的返回。

2.11.9 抛出异常

   在程序中抛出异常使用athrow指令。多个java虚拟机指令在检测到异常情况是也会抛出异常。

2.11.10 同步

  java虚拟机支持方法同步和方法中一系列指令同步,同步使用同步结构:monitor

  方法级别的同步是隐式的,作为方法调用和返回的一部分。方法调用指令检查运行时常量池中的method_info结构中的ACC_SYNCHRONIZED标识来区分一个方法是不是同步方法。当调用一个设置了ACC_SYNCHRONIZED标识的方法,执行线程进入monitor,调用这个方法,当方法不管是正常或者异常调用结束时,线程退出monitor。在执行这个方法的时间段内,只有执行的线程拥有这个monitor,没有其他线程可以进入。当调用同步方法时抛出异常,并且在同步方法内部没有处理这个异常,那么这个方法的monitor将在同步方法向外抛出异常前自动退出。

  指令序列的同步通常使用java编程语言中的synchronized块。java虚拟机提供monitorenter和monitorexit指令来支持这个语法。正确实现synchronized需要编译器和java虚拟机合作完成。

结构化锁定(Structured locking)是指在方法调用期间每一个给定的monitor退出都将匹配这个这个monitor之前的进入的情形。由于没法保证所有提交给java虚拟机的代码都会满足结构化锁定,java虚拟机的实现者允许(不是必需的)强制执行下面两条规则来保证结构化锁定。其中T表示一个线程,M表示一个monitor。

  1、当方法调用完成时,不管时正常结束还是异常退出,T进入M的次数必需和T离开M的次数相等。

  2、在方法调用过程中,任何时刻都不会出现T离开M的次数多余T进入M的次数。

  注意,当调用一个同步方法时,java虚拟机自动执行monitor的进入和退出也被认为时在方法调用期间完成。

 

2.12 类库

  java虚拟机对于java se平台实现类库必须提供足够的支持。这些类库中的一些类如果没有java虚拟机的协作将无法实现。

  支持以下功能的类可能需要java虚拟机的特殊支持:

  •   反射,例如java.lang.reflect中的类和Class类。
  •   加载和创建一个类或者接口。上面提到的列子也适用于这点
  •   链接和初始化一个类或者接口。上面提到的列子也适用于这点
  •   安全,例如java.security包中的类和其它例如SecurityManager的类
  •   多线程,例如Thread类
  •   弱引用,例如java.lang.ref包中的类。

  上面的列表是为了说明问题,而不是全面的介绍类库。这些类或它们提供的功能的详尽列表超出了本规范的范围。有关详细信息,请参阅Java SE平台类库的规范。

posted on 2020-03-27 16:06  lfw123  阅读(202)  评论(0编辑  收藏  举报

导航