zoukankan      html  css  js  c++  java
  • JVM字节码指令

    引言

      Java虚拟机的指令是由一个字节长度的代表着某种特定操作含义的数字(操作码)以及跟在其后零至多个操作所需参数(操作数)构成。

    加载和存储指令

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

      将一个局部变量加载到操作数栈顶: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

    运算指令

      用于对两个操作数栈上的值进行某种特定的运算,并把结果压入栈顶。主要是两种:对整数的运算和对浮点数的运算。

    类型转换指令

      Java虚拟机直接支持宽化类型转化(int转化为double),处理窄化类型转化(double转化为int)必须使用类型转化指令如 d2i 等。

    对象创建和访问指令

      创建类实例和数组使用的不同的指令。

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

      访问类字段和实例字段的指令:getField,putField和getStatic,putStatic。

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

      把操作数栈顶的值存储到数组的指定元素中:iastore,aastore。

      取数组长度:arraylength。

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

    操作数栈管理指令

      将操作数栈的一个或者是两个元素出栈:pop,pop2。

      复制栈顶一个或两个元素并将复制元素压入栈顶:dup(不能是long或double类型),dup2等。

      栈顶的两个数值交换:swap。

    控制转移指令

      该类指令可以让Java虚拟机有条件或无条件的到指定位置执行程序,而不是该类指令的下一条指令处执行程序。

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

      符合条件分支:tableswitch,lookupswitch。

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

    方法调用和返回指令

    方法调用指令

      invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)。 

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

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

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

      invokedynamic指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前四条指令的分派逻辑固化在虚拟机中,而该指令的分派逻辑由用户设定的引导方法决定。

    方法返回指令

      ireturn,areturn,return。

    异常处理指令

      显示的抛出异常(throw语句)都是由athrow指令实现。处理异常(catch语句)采用异常表实现。

    同步指令

       方法级同步无需使用指令控制,他实现在方法调用和返回操作中。方法调用时,指令会检查方法的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

  • 相关阅读:
    开放API接口安全处理!
    ant笔记
    并发调试
    IDEA 设置(中文乱码、svn、热部署、ideolog 、Jrebel )
    win10家庭版升级专业版
    org.json package
    'root'@'localhost'不能登录问题
    javascript之DOM选择符
    javascript之DOM(四其他类型)
    javascript之DOM(三Element类型)
  • 原文地址:https://www.cnblogs.com/BINGJJFLY/p/7676591.html
Copyright © 2011-2022 走看看