JVM字节码指令,Java虚拟机规范

此处的编写翻译是指Java语言编写翻译成Java虚构机指令集的编写翻译器。指令格式:

引言

  Java设想机的下令是由一个字节长度的意味着某种特定操作含义的数字(操作码)以及跟在其后零至八个操作所需参数(操作数)构成。

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

加载和仓库储存指令

  该类指令用于将数据在栈帧中的局地变量表和操作数栈之间往来传输。

  将三个有些变量加载到操作数栈顶:iload,iload_<n>等,当中iload的前三个部分变量可以运用iload_0,iload_1,iload_2,iload_3代表,更加的多的变量用
iload  N 代表,非静态方法的首先个变量是this命令为aload_0。

  将一个数值从操作数栈存款和储蓄到一些变量表:istore,istore_<n>等。

  将一个常量加载到操作数栈顶:iconst_<n>,bipush,ldc等。

    const类别命令:担当把差相当的少的数值类型送到操作数栈顶,int型只可以把-1,0,1,2,3,4,5(分别使用iconst_m1,iconst_0至

            
iconst_5)送到栈顶,其余的数值使用push种类命令。

    push类别命令:担负把二个整形数字(一定限制内)送到到操作数栈顶,超出钦点范围使用 ldc 种类命令。bipush表示将单词

           
节的常量值(-128~127)推送至栈顶,sipush代表将多少个短整型常量值(-32768~32767)推送至栈顶。

    ldc体系指令:担当把数值常量或String常量值从常量池中推送至操作数栈顶,对于const种类命令与push类别命令操作范围之外

           的数值类型常量还会有String字符串(非new情势)都坐落常量池中。ldc(将int,
float或String型常量值从常量池

           中推送至栈顶)、ldc_w(将int,
float或String型常量值从常量池中推送至栈顶(宽索引))、ldc2_w(将long

           或double型常量值从常量池中推送至栈顶(宽索引))。

  扩大局部变量表的拜会索引的命令:wide

  当中nop 指令代表怎么着都不做,aconst_null 指令代表将null值推送至栈顶。

public int function() throws Exception {    
        int x;
        x = 2;
        int i = 3;
        int j = 100;
        double d = 1.0D;
        double dd = 88.88D;
        float f = 1.8F;
        String s = "A";
        return i;
}

0: iconst_2  // 将整数2压入栈顶
1: istore_1  // 将栈顶的整数2积攒到部分变量表,即存款和储蓄到了x变量中
2: iconst_3  // 将整数3压入栈顶
3: istore_2  // 将栈顶的大背头3囤积到有个别变量表
4: bipush 100 // 将整数100压入栈顶
6: istore_3  // 将栈顶的整数100存款和储蓄到一些变量表
7: dconst_1  // 将double类型数值1压入栈顶
8: dstore 4  // 将栈顶的double类型数值1储存到部分变量表
10: ldc2_w #2 // 将常量池中的double类型的88.88数值压入栈顶
13: dstore 6 // 将double类型的数值88.88囤积到有个别变量表
15: ldc #4   // 将常量池中的float类型的1.8数值压入栈顶
17: fstore 8  // 将float类型的数值1.8存款和储蓄到一些变量表
19: ldc #5   // 将常量池中的String字符串”A”压入栈顶
21: astore 9 // 将String字符串”A”存款和储蓄到某些变量表
23: iload_2  // 将整数2压入栈顶
24: ireturn  // 重返int类型的栈顶数据

package com.wjz.clazz;
public class MyClass {
    final String names[]={"wjz","hb"};
    public void function() throws Exception {
        String str=names[0];
    }
}

0: aload_0    // 将this引用推送至栈顶
1: getfield #5  // 获得常量池中的第5个常量即字段表的字段引用压入栈顶
4: iconst_0   // 将数组的索引值(下标)推至栈顶
5: aaload     // 根据栈里内容来把name数组的第一项的值推至栈顶
6: astore_1    // 把栈顶的值存到str变量里
7: return     // 方法结束,返回值类型为void

package com.wjz.clazz;
public class MyClass {
    public void function() throws Exception {
        int moneys[]=new int[5];
        moneys[3]=100;
    }
}

0: iconst_5    // 将数组长度压入栈顶
1: newarray int   // 创建int类型数组
3: astore_1    // 将栈顶数据即数组长度存储在局部变量表中
4: aload_1     // 将数组引用压入栈顶
5: iconst_3    // 将数组下标压入栈顶
6: bipush 100   // 将整数100压入栈顶
8: iastore     // 将栈顶数据即100存储在局部变量表的数组的指定索引位置
9: return     // 方法结束,返回值类型为void

index为命令操作码的目录,也能够感到相对于方法开端处的偏移量。在象征运营时常量池索引的操作数前会以”#”开头。

运算指令

  用来对五个操作数栈上的值实行某种特定的演算,并把结果压入栈顶。主假使三种:对整数的演算和对浮点数的演算。

10 ldc #1 //Push float constant 100.0

类型调换指令

  Java虚拟机间接帮忙宽化类型转化(int转化为double),管理窄化类型转化(double转化为int)必得运用项目转化指令如 d2i
等。

三个简约的for循环例子,成对应字节码的施行进程,这是三个驾驭字节码实施相比好的例子。

对象创造和拜望指令

  成立类实例和数组使用的不等的通令。

  创设实例指令为:new,创设数组指令为:newarray。

  访问类字段和实例字段的指令:getField,put菲尔德和getStatic,putStatic。

  把数组的二个要素压入操作数栈:iaload,aaload。

  把操作数栈顶的值存款和储蓄到数组的钦点成分中:iastore,aastore。

  取数高管度:arraylength。

  检查类实例类型:instanceof,checkcast。

void spin() { int i = 0; for(i = 0; i < 100;i ++ ) { }}//对应的字节码(编译器实现可能不同)Method void spin()0 iconst_01 istore_12 goto 85 iinc 1 18 iload_19 bipush 10011 if_icmplt 5

操作数栈管理指令

  将操作数栈的多少个照旧是三个成分出栈:pop,pop2。

  复制栈顶二个或四个成分并将复制作而成分压入栈顶:dup(不可能是long或double类型),dup2等。

  栈顶的两个数值沟通:swap。

图片 1jvm_for_loop1

支配转移指令

  该类指令可以让Java虚构机有原则或无条件的到钦点地方施行顺序,并非此类指令的下一条指令处实行顺序。

  条件分支:ifeq,ifgt,ifnull等。

  切合条件分支:tableswitch,lookupswitch。

  无条件分支:goto,jsr,ret等。

iconst_0操作码隐式包括了int类型0操作数,表示将int型0值压入操作数栈,那样没有需求特地为入栈操作保存叁个立即操作数的值,防止读取解析操作数,简单急忙。倘诺运用bitpush
0将会增添多个字节的尺寸。类似指令还应该有 iconst_m1 iconst_1
…iconst_5。

办法调用和重临指令

图片 2jvm_for_loop2

措施调用指令

  invokevirtual指令用于调用对象的实例方法,依照目的的实在类型实行摊派(虚方法分派)。 

  invokeinterface指令用于调用接口方法,它会在运作时寻找三个贯彻了该接口方法的对象,找到确切的情势开展调用。

  invokespecial指令用于调用一些亟待新鲜管理的实例方法,包涵实例开始化方法、私有方法和父类方法。

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

  invokedynamic指令用于在运维时动态分析出调用点限定符所援引的方法,并执行该情势。前四条指令的摊派逻辑固化在虚构机中,而该指令的分摊逻辑由客商设定的教导格局决定。

istore_1一律隐式包涵了int类型1操作数,表示从操作数栈栈顶弹出贰个int类型的值,寄放到第一个部分变量里面。(为啥是率先个吗,因为那是叁个实例方法第0个部分变量恒久是前段时间目的this)。

主意重临指令

  ireturn,areturn,return。

图片 3jvm_for_loop3

极其处理指令

  展现的抛出相当(throw语句)都以由athrow指令实现。管理极度(catch语句)采纳极度表完毕。

义务医治跳转到偏移量为8的一声令下段实行,第叁次循环量i的值是不加的

一道琼斯指数令

   方法级同步不供给采用指令调节,他达成在议程调用和再次来到操作中。方法调用时,指令会检查措施的ACC_SYNCHRONIZED访谈标识是或不是设置了,要是设置了试行线程须求先具有管程技艺举行情势,方法成功时释放管程,方法执行时期,实行线程获得了管程,别的线程不能够再获得一致管程。同步方法持有的管程在非常抛出到方法之外时自动释放管程。

  同步一段指令集系列时,虚构机使用monitorenter和monitorexit指令实现同步。

package com.wjz.clazz;
public class MyClass {
    public void function(Object obj) throws Exception {
        synchronized (obj) {
            dosomething();
        }
    }
    private void dosomething() {}
}

0: aload_1        // 将对象obj引用入栈        
1: dup           // 复制栈顶元素即obj引用 
2: astore_2       // 将栈顶元素(obj引用)存储到局部变量表Slot 2中
3: monitorenter     // 以栈顶元素作为锁,开始同步
4: aload_0        // 将局部变量表的Slot 0(this引用)入栈
5: invokespecial #2  // 调用dosomething()方法
8: aload_2        // 将局部变量表Slow 2(obj引用)元素入栈
9: monitorexit      // 退出同步
10: goto       18    // 方法正常结束,跳转到18返回
13: astore_3       // 将栈顶元素(异常对象引用)存储到局部变量表Slot 3中,这步开始是异常路径,见异常表Target 13
14: aload_2       // 将局部变量表Slow 2(obj引用)元素入栈
15: monitorexit     // 退出同步
16: aload_3       // 将局部变量表Slow 3(异常对象引用)元素入栈
17: athrow        // 把异常对象重新抛出给function()方法的调用者
18: return        // 方法正常返回

 Exception table:
 from to target type
  4  10   13    any
  13 16   13    any

 

图片 4jvm_for_loop4

iload_1等同满含隐式操作数,表示将第四个部分变量的值加载到操作数栈,bitpush
100象征将int类型100值压入栈。

图片 5jvm_for_loop5

if_icmplt
5表示将操作数栈弹出,相比较两位的值借使i小于100则跳转到偏移量为5的地点试行,否则实践return操作。

图片 6jvm_for_loop6

少数局地变量须求频仍操作,Java虚构机也做了对应的支撑,iinc 1
1的效应是对第一个部分变量加1操作。

图片 7jvm_for_loop7

接下来继续重复下面的操作,直到局地变量的值操作100。

Java设想机是依靠栈架构划设想计的,大非常多操作从脚下栈幀的操作数栈收取1个只怕多个操作数,如若指令有总括结果压入操作数栈。没调用一个办法都会创立三个新的栈幀,并创建对应措施所需的操作数栈和部分变量表。

将地点的i的数据类型换到short,double有不一致的编写翻译代码,由于Java设想机指令不能越过256条,不容许对每一种数据类型操作都尽善,比如紧缺对byte
char
short数据类型间接操作的支撑,必要转移为int类型,代价正是将他们长度扩大为4字节。对于浮点型,缺少条件转移指令。

Java虚构机基于操作数栈来进展算术运算,运算指令的操作数从操作数栈弹出,若是有运算结果会被放回操作数栈(除了后面提到的iinc指令直接对一些变量表进行操作)。

过多数值常量,对象,字段,方法都是经过当前类的运维时常量进行拜访。ldc指令用于访谈运维时常量池中的对象,当使用的周转时常量多于2五十四个时,用ldc_w来取代访谈。特别地,当访谈类型为double和long的运行时常量池项使用ldc2_w。

while语句编写翻译:

void whileInt(){ int i = 0; while(i < 100) { i++; }}//编译后的代码 0: iconst_0 1: istore_1 2: iload_1 3: bipush 100 5: if_icmpge 14 8: iinc 1, 111: goto 214: return

设想机对各类数据结构的调控结构选取了平日的办法编写翻译,只是遵照差异数据类型使用差别的一声令下访谈。这么做多少会使编写翻译代码作用下落,必要越来越多的下令来贯彻相应的数据类型判别。浮点型数据的可比指令:float—–fcmpl和fcmpg,double—–dcmpl和dcmpg,语义相似,对待NaN(Not
A Number)变量时有所不一致,具体参照他事他说加以考察指令描述。

假定传递了n个参数给某些实例方法,则当前的栈幀做将它他们保存到第四个到第n个部分变量中,因为第0个部分变量时当前指标的援引this。假诺是静态方法规从第0个部分变量开端。

实例方法:

int add(int i,int j) { return i + j;}//------------------- 0: iload_1 1: iload_2 2: iadd 3: ireturn

静态方法:

static int sub(int i,int j) { return i - j;}//-------------------0: iload_01: iload_12: isub3: ireturn

invokevirtual 普通方法实例调用依据运转时对象类型进行分摊,也正是多态:

int add2(int i,int j) { return add;}//----------------------- 0: aload_0 1: iload_1 2: iload_2 3: invokevirtual #8 // Method add:I 6: ireturn

aload_0加载局地变量this到操作数栈,iload_1
iload_2加载第3个部分变量和第叁个部分变量到操作数栈,invokevirtual调用实例方法,ireturn会把近期操作数栈的栈顶值(便是add方法的重返值)压入调用add2()方法的操作数栈,然后切换栈幀。

invokestatic指令用于调用类的静态方法,比较像样,操作数栈里面无需this变量:

int add3(int i,int j){ return addStatic;}int addStatic(int i,int j) { return i + j;}//-------------------------// int add3; 0: iload_1 1: iload_2 2: invokestatic #9 // Method addStatic:I 5: ireturn

invokespecial用来调用父类方法和民用方法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注