虚拟机指令集


​ Java虚拟机既然是一台虚拟的计算机,那必有一套完善指令集,指令由一个字节长度,代表某种特定操作含义的操作码构成。这里在最初接触计算机的时候,我是有一些疑问的。例如为什么计算机都有它的指令集,这些指令是怎么被CPU执行的。由于我当初学的是电子科学,包含模电数电,单片机等内容,其中单片机就是一个小型的片上系统,随着学习的深入这些疑问自然就迎刃而解了。我谈一下自己的理解,也顺便做一下记录。

​ 对于CPU而言它只认识0和1,对应的是数字电路中的高低电平,实现原理是把MOS管当做开关使用,MOS管是一种半导体晶体管,主要成分硅(这也是美国硅谷这个名字的由来吧)。通过MOS管可以实现各种寄存器,累加器,计数器,与非门,或非门等逻辑门电路。每一条指令都是特定的门电路组成的,门电路是CPU的基本工作单元,数以亿计的门电路才造就了CPU的强大运算能力,这些门电路输入是一位或多位二进制数,输出也是一位或多位二进制数。在最早的计算机上,程序员需要将0101这种二进制代码给CPU计算,这里的二进制代码会控制CPU进行加减乘除或者逻辑运算,对应于在MOS管上的状态就是开和关,这种二进制代码也叫机器语言,是计算机能直接读懂的语言。但是这对于程序员来说简直是噩梦,一个小小的计算功能,用机器语言来编写需要写一大串0和1。最关键的是这些0和1代码代表什么意思也非常难以记忆,开发成本很大。于是科学家们用了一种高级的语言来代替它 ,然后汇编语言诞生了,汇编语言更接近人类自然语言的习惯。然后再做一个编译器工具帮我们把汇编语言翻译成机器语言就行了。

​ 那么问题来了,编译器按照什么规则来翻译汇编代码呢?答案就是指令集,指令集是由设计CPU的生产厂商给出。CPU指令集是描述CPU能实现什么功能的一个集合, 就是描述”CPU能使用哪些机器码”的集合。指令集的作用, 就是告诉程序员或者说编译器, 汇编一定要有什么格式. 支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 都是指令集规范的内容, 要是写错了, 就无法翻译成机器码。不同的CPU生产厂商产生了不同的指令集,Intel阵营的8086指令集,ARM阵营的RISC指令集。不同阵营,其对应的汇编语言也是不一样的。

1 . JVM的指令集

​ 前面说了那么多,都是为这里做铺垫。作为一个Java程序员怎么能不认识虚拟机的指令集,指令集不仅可以帮我们看懂字节码文件,同时也有助于理解JVM的运行原理。我总结了一下JVM的指令集,主要分为以下几种类型的指令。

(1) . 空指令

字节码 指令助记符 指令含义
0x00 nop 什么都不做

nop指令相信接触过汇编语言的都知道,它什么都不做但是占用一个指令周期的时间。指令周期的时间不是固定的,不同的指令其指令周期时间不同,一个指令周期由如干个机器周期组成,每个机器周期能完成一项基本操作,比如取指令、存储器读、存储器写等。而机器周期又由如干个时钟周期组成,在一个时钟周期内,CPU只能完成一个最基本的动作,一个时钟周期的时间计算也比较简单,例如CPU的频率是2.7GHz,这里要注意换算单位,频率单位1GHz =1000MHz,1MHz就是1百万赫兹,所以频率为2.7GHz的CPU。一个时钟周期为 1/2700000000 秒 。 显然,时钟频率越高,CPU的工作速度就越快,这也是我们购买CPU时的一个重要指标。

(2) . 基本数据类型的取值和存储指令

字节码 指令助记符 指令含义
0x01 aconst_null 将null推送至栈顶
0x02 iconst_m1 将int型-1推送至栈顶
0x0i(i=3,4,5,6,7,8) iconst_j(j=0,1,2,3,4,5) 将int型 j 推送至栈顶
0x0i(i=9,a) lconst_j(j=0,1) 将long型 j 推送至栈顶
0x0i(i=b,c,d) fconst_j(j=0,1,2) 将float型 j 推送至栈顶
0x0i(ie,f) dconst_j(j=0,1) 将double型 j 推送至栈顶
0x10 bipush 将单字节常量(-127~128)推送至栈顶
0x11 sipush 将短整型常量(-32768~32767)推至栈顶
0x12 ldc 将int,float,String常量从常量池中推至栈顶
0x13 ldc_w 将int,float,String常量从常量池中推至栈顶(宽索引)
0x14 ldc2_w 将long,double常量从常量池中推至栈顶(宽索引)
0x15 iload 将指定的int型局部变量推至栈顶
0x16 lload 将指定的long型局部变量推至栈顶
0x17 fload 将指定的float型局部变量推至栈顶
0x18 dload 将指定的double型局部变量推至栈顶
0x1i(i=a,b,c,d) iload_j(j=0,1,2,3) 将第 j 个int型的局部变量推至栈顶
0x1i(i=e,f) lload_j(j=0,1) 将第 j 个long 型局部变量推至栈顶
0x2i(i=0,1) lload_j(j=3,4) 将第 j 个long 型局部变量推至栈顶
0x2i(i=2,3,4,5) fload_j(j=0,1,2,3) 将第 j 个float 型局部变量推至栈顶
0x2i(i=6,7,8,9) dload_j(j=0,1,2,3) 将第 j 个double 型局部变量推至栈顶
0x36 istore 将栈顶 int 型数值存入指定的本地变量
0x37 lstore 将栈顶 long 型数值存入指定的本地变量
0x39 dstore 将栈顶 dloat 型数值存入指定的本地变量
0x38 fstore 将栈顶 float 型数值存入指定的本地变量
0x3i(i=b,c,d,e) istore_j(j=0,1,2,3) 将栈顶 int 型数值存入第 j 个本地变量
0x3f lstore_0 将栈顶 long 型数值存入第 0 个本地变量
0x4i(i=0,1,2) lstore_j(j=1,2,3) 将栈顶 long 型数值存入第 j 个本地变量
0x4i(i=3,4,5,6) fstore_j(j=0,1,2,3) 将栈顶 float 型数值存入第 j 个本地变量
0x4i(i=7,8,9,a) dstore_j(j=0,1,2,3) 将栈顶 double 型数值存入第 j 个本地变量
0xc4 wide 扩展本地变量的宽度

总的概括来说就是

将一个本地变量加载到操作数栈的指令有

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

这里要注意的是随着数值取值范围的改变,指令会随之改变。例如当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令

(3) . 引用数据类型的取值与存储操作

字节码 指令助记符 指令含义
0x19 aload 将指定的引用数据类型本地变量推至栈顶
0x2i(i=a,b,c,d) aload_j(j=0,1,2,3) 将第 j 个引用类型本地变量推至栈顶
0x2e iaload 将int型数组指定的索引的值推至栈顶
0x2f laload 将long型数组指定的索引的值推至栈顶
0x30 faload 将float型数组指定的索引的值推至栈顶
0x31 daload 将double型数组指定的索引的值推至栈顶
0x32 aaload 将引用数据类型数组指定的索引的值推至栈顶
0x33 baload 将boolean或byte型数组指定的索引的值推至栈顶
0x34 caload 将char型数组指定的索引的值推至栈顶
0x35 saload 将short型数组指定的索引的值推至栈顶
0x3a astore 将栈顶的引用型数值存入指定的本地变量
0x4i(i=b,c,d,e) astore_j(j=0,1,2,3) 将栈顶引用型数值存入第 j 个本地变量
0x4f iastore 将栈顶 int 型数值存入指定数组的指定索引位置
0x50 lastore 将栈顶 long 型数值存入指定数组的指定索引位置
0x51 fastore 将栈顶 float 型数值存入指定数组的指定索引位置
0x52 dastore 将栈顶 double 型数值存入指定数组的指定索引位置
0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55 castore 将栈顶 char 型数值存入指定数组的指定索引位置
0x56 sastore 将栈顶 short 型数值存入指定数组的指定索引位置
0x57 pop 将栈顶弹出(数值不能是 long 或 double类型)
0xbb new 创建一个对象,并将其压入栈顶
0xbc newarray 创建一个基本数据类型(如int,char)的数组,并将其引用的值压入栈顶
0xbd anewarray 创建一个引用型数组(如类,接口,数组),并将其引用的值压入栈顶
0xc5 multianewarray 创建指定类型和指定维度的多维数组,并将其引用值压入栈顶
0xbe arraylength 获取数组长度值并压入栈顶
0xc0 checkcast 校验类型转换,校验未通过抛出ClassCastException
0xc1 instanceof 校验对象是否是指定的类的实例,如果是,则将1压入栈顶,否则将0压入栈顶
0xb2 getstatic 获取指定类的静态域,并将其值压人栈顶
0xb3 putstatic 为指定的类的静态域赋值
0xb4 getfield 获取指定类的实例域,并将其压入栈顶
0xb5 putfield 为指定类的实例域赋值

这里总结概括一下,创建实例的指令

new

创建数组对象以及获取数组长度的指令

newarray、anewarray、multianewarray、arraylength

访问类字段(static字段就是类变量)和实例字段(非static字段就是实例变量)指令

getfield、putfield、getstatic、putstatic

把一个数组中指定索引处的值加载到操作数栈的指令

baload、caload、saload、iaload、laload、faload、daload、aaload

将一个操作数栈的值储存到数组中指定索引处的指令

bastore、castore、sastore、iastore、fastore、dastore、aastore

检查类实例类型的指令

instanceof、checkcast

虽然实例和数组都是对象,但是虚拟机对实例和数组实例的创建与操作使用了不同的字节码指令

(4) . 操作数栈管理指令

字节码 指令助记符 指令含义
0x57 pop 将栈顶弹出(数值不能是 long 或 double类型)
0x58 pop2 将栈顶一个(对于 long 或double类型)或两个(非 long 或double类型)数值弹出
0x59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5f swap 将栈最顶端的两数值互换(数值不能是 long 或 double类型)
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(对于 long 或double类型)或两个(非 long 或double类型)值并压栈
0x5d dup2_x1 dup_x1 指令的双倍版本
x05e dup2_x2 dup_x2 指令的双倍版本

(5) . 控制转移指令

字节码 指令助记符 指令含义
0x99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifnq 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值小于或等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于或等于0时跳转
0x9f if_icmpeq 比较栈顶两个int型数值的大小,结果等于0时跳转
0xa0 if_icmpne 比较栈顶两个int型数值的大小,结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两个int型数值的大小,结果小于0时跳转
0xa2 if_icmpge 比较栈顶两个int型数值的大小,结果大于或等于0时跳转
0xa3 if_icmpgt 比较栈顶两个int型数值的大小,结果大于0时跳转
0xa4 if_icmple 比较栈顶两个int型数值的大小,结果小于或等于0时跳转
x0a5 if_acmpeq 比较栈顶两个引用型数值,结果相等时跳转
0xa6 if_acmpne 比较栈顶两个引用型数值,结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定的16位offset位置,并将jsr的下一条指令地址压入栈顶
0xa9 ret 返回至本地变量指定的index的指令位置(一般与 jsr 或 jsr_w联合使用)
0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令)
0xc6 ifnull 为null时跳转
0xc7 ifnonull 不为null时跳转
oxc8 goto_w 无条件跳转(宽索引)
0xc9 jsr_w 跳转至指定32位offset位置,并将jsr_w的下一条指令地址压入栈顶

条件分支

ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、 if_icmpgt、if_icmple、if_icmpge、if_acmpeq、if_acmpne

复合条件分支

tableswitch、lookupswitch

无条件分支

goto、goto_w、jsr、jsr_w、ret

Java条件分支比较包括三种类型值的比较,int类型,reference 类型,null 值。而其它boolean 类型、byte 类型、char 类型和 short 类型的条件分支比较操作,都使用 int 类型的比较指令来完成,而对于 long 类型、float 类型和 double 类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行 int 类型的条件分支比较操作来完成整个分支跳转。由于各种类型的比较最终都会转化为 int 类型的比较操作,所以Java 虚拟机提供了非常丰富的 int类型的条件分支指令。另外所有 int 类型的条件分支转移指令进行的都是有符号的比较操作。

(6) . 线程同步

字节码 指令助记符 指令含义
0xc2 monitorenter 获得对象的锁,用于同步方法或同步代码块
0xc3 monitorexit 释放对象的锁,用于同步方法或同步代码块

Java 虚拟机可以支持方法级的同步和方法内一段代码块的同步,这两种同步结构都是使用监视器 (monitor)来支持实现的。

方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构(method_info Structure)中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放

(7) . 运算指令

字节码 助记符 指令含义
0x60 iadd 将栈顶两个 int 型数值相加并将结果压入栈顶
0x61 ladd 将栈顶两个 long 型数值相加并将结果压入栈顶
0x62 fadd 将栈顶两个 float 型数值相加并将结果压入栈顶
0x63 dadd 将栈顶两个 double 型数值相加并将结果压入栈顶
0x64 isub 将栈顶两个 int 型数值相减并将结果压入栈顶
0x65 lsub 将栈顶两个 long 型数值相减并将结果压入栈顶
0x66 fsub 将栈顶两个 float 型数值相减并将结果压入栈顶
0x67 dsub 将栈顶两个 double 型数值相减并将结果压入栈顶
0x68 imul 将栈顶两个 int 型数值相乘并将结果压入栈顶
x069 lmul 将栈顶两个 long 型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两个 float 型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两个 double 型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两个 int 型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两个 long 型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两个 float 型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两个 double 型数值相除并将结果压入栈顶
0x70 irem 将栈顶两个int 型数值作取模运算并将结果压入栈顶
0x71 lrem 将栈顶两个long 型数值作取模运算并将结果压入栈顶
0x72 frem 将栈顶两个float 型数值作取模运算并将结果压入栈顶
0x73 drem 将栈顶两个double 型数值作取模运算并将结果压入栈顶
0x74 ineg 将栈顶两个int 型数值取负并将结果压入栈顶
0x75 lneg 将栈顶两个long 型数值取负并将结果压入栈顶
0x76 fneg 将栈顶两个float 型数值取负并将结果压入栈顶
0x77 dneg 将栈顶两个double 型数值取负并将结果压入栈顶
0x78 ishl 将 int 型数值取左移指定位数并将结果压入栈顶
0x79 lshl 将 long 型数值取左移指定位数并将结果压入栈顶
0x7a ishr 将 int 型数值(带符号)取右移指定位数并将结果压入栈顶
0x7b lshr 将 long 型数值(带符号)取右移指定位数并将结果压入栈顶
0x7c iushr 将 int 型数值(无符号)取右移指定位数并将结果压入栈顶
0x7d lushr 将 long 型数值(无符号)取右移指定位数并将结果压入栈顶
0x7e iand 将栈顶两 int 型数值作 按位与 并将结果压入栈顶
0x7f land 将栈顶两 long 型数值作 按位与 并将结果压入栈顶
0x80 ior 将栈顶两 int 型数值作 按位或 并将结果压入栈顶
0x81 lor 将栈顶两 long 型数值作 按位或 并将结果压入栈顶
0x82 ixor 将栈顶两 int 型数值作 按位异或 并将结果压入栈顶
0x83 lxor 将栈顶两 long 型数值作 按位异或 并将结果压入栈顶
0x84 iinc 将指定int 型数值增加指定值(如 i++,i–,j += 1等)
0x94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0x95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96 fcmpg 比较栈顶两 float 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97 dcmpl 比较栈顶两double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98 dcmpg 比较栈顶两 double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶

概括来说就是下面这几种分类

加法指令: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 虚拟机没有明确规定整型数据溢出的情况,但是规定了在处理整型数据时,只有除法指令(idiv 和 ldiv)以及求余指令(irem 和 lrem)出现除数为零时会导致虚拟机抛出异常,如果发生了这种情况,虚拟机将会抛出 ArithmeitcException 异常。 例如下面的代码

public class Test {

    public static void main(String[] args){
        int num = Integer.MAX_VALUE ;        //获得int的最大值
        System.out.println("num = "+num);
        System.out.println("num+1 = "+(num+1));
        System.out.println("num+2 = "+(num+2));
    }
}

得到的结果

num = 2147483647
num+1 = -2147483648
num+2 = -2147483647

这段代码获取了int型数据的最大值,并在最大值的基础上进行了加法运算,程序不会报错,但结果和预期却截然相反,其原因就是整型数据出现了溢出。

Java 虚拟机在处理浮点数运算时,不会抛出任何运行时异常,当一个操作产生溢出时,将会使用有符号的无穷大来表示,如果某个操作结果没有明确的数学定义的话,将会时候 NaN 值来表示。所有使用 NaN 值作为操作数的算术操作,结果都会返回 NaN 。

(8) . 类型转换指令

字节码 指令助记符 指令含义
0x85 i2l 将栈顶 int 型数值强制转换成 long 型数值并将结果压入栈顶
0x86 i2f 将栈顶 int 型数值强制转换成 float 型数值并将结果压入栈顶
0x87 i2d 将栈顶 int 型数值强制转换成 double 型数值并将结果压入栈顶
0x88 l2i 将栈顶 long 型数值强制转换成 int 型数值并将结果压入栈顶
0x89 l2f 将栈顶 long 型数值强制转换成 float 型数值并将结果压入栈顶
0x8a l2d 将栈顶 long 型数值强制转换成 double 型数值并将结果压入栈顶
0x8b f2i 将栈顶 float 型数值强制转换成 int 型数值并将结果压入栈顶
0x8c f2l 将栈顶 float 型数值强制转换成 long 型数值并将结果压入栈顶
0x8d f2d 将栈顶 float 型数值强制转换成 double 型数值并将结果压入栈顶
0x8e d2i 将栈顶 double 型数值强制转换成 int 型数值并将结果压入栈顶
0x8f d2l 将栈顶 double 型数值强制转换成 long 型数值并将结果压入栈顶
0x90 d2f 将栈顶 double 型数值强制转换成 float 型数值并将结果压入栈顶
0x91 i2b 将栈顶 int 型数值强制转换成 byte 型数值并将结果压入栈顶
0x92 i2c 将栈顶 int 型数值强制转换成 char 型数值并将结果压入栈顶
0x93 i2s 将栈顶 int 型数值强制转换成 short 型数值并将结果压入栈顶

这里需要注意的是强制转换可能会丢失精度的问题

(9) . 方法调用与返回指令

字节码 指令助记符 指令含义
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba invokedynamic 调用动态方法
0xac ireturn 从当前方法返回 int
0xad lreturn 从当前方法返回 long
0xae freturn 从当前方法返回 float
0xaf dreturn 从当前方法返回 double
0xb1 areturn 从当前方法返回对象引用

invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。 返回类型中 ,返回类型为boolean、byte、char、short 和 int 类型时都通过ireturn指令实现。

(10) . 异常

字节码 指令助记符 指令含义
0xbf athrow 将栈顶的异常抛出

2 . 如何通过class文件查看指令代码

这个方法通过一个指令即可,以windows系统为例,首先打开windows的终端,然后进入到.class文件所在的目录,通过 javap -c 文件名 即可查看指令代码。例如下面的代码

public class Test {

    public static void main(String[] args){
        int m2 = -1;
        int m0 =5;
        int n = 2;
        int m = 6;
        int a1 = 128;
        int a2 = 32768;
        long k1 = 1;
        long k2 = 5;
        double d = 1;
        Integer ii = new Integer(8);
        try{
            synchronized (Test.class){
                int x = m/n;
            }
        }catch (NullPointerException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

然后通过打开终端

回车之后就可以看到指令代码了,具体结果如下

F:\workSpace\ideaSpace\busted\out\production\busted\com\lsj\test>javap -c Test
Compiled from "Test.java"
public class com.lsj.test.Test {
  public com.lsj.test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_m1
       1: istore_1
       2: iconst_5
       3: istore_2
       4: iconst_2
       5: istore_3
       6: bipush        6
       8: istore        4
      10: sipush        128
      13: istore        5
      15: ldc           #2                  // int 32768
      17: istore        6
      19: lconst_1
      20: lstore        7
      22: ldc2_w        #3                  // long 5l
      25: lstore        9
      27: dconst_1
      28: dstore        11
      30: new           #5                  // class java/lang/Integer
      33: dup
      34: bipush        8
      36: invokespecial #6                  // Method java/lang/Integer."<init>":(I)V
      39: astore        13
      41: ldc           #7                  // class com/lsj/test/Test
      43: dup
      44: astore        14
      46: monitorenter
      47: iload         4
      49: iload_3
      50: idiv
      51: istore        15
      53: aload         14
      55: monitorexit
      56: goto          67
      59: astore        16
      61: aload         14
      63: monitorexit
      64: aload         16
      66: athrow
      67: goto          87
      70: astore        14
      72: aload         14
      74: invokevirtual #9                  // Method java/lang/NullPointerException.printStackTrace:()V
      77: goto          87
      80: astore        14
      82: aload         14
      84: invokevirtual #11                 // Method java/lang/Exception.printStackTrace:()V
      87: return
    Exception table:
       from    to  target type
          47    56    59   any
          59    64    59   any
          41    67    70   Class java/lang/NullPointerException
          41    67    80   Class java/lang/Exception
}

通过指令代码就能看到java代码编译之后每一步究竟做了什么事情了,也方便我们去解决问题。


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
CAP理论 CAP理论
​ 使用了一段时间的Hbase,一直没有时间去了解它的理论基础。最近在看分布式的一些原理,其中最著名的分布式理论就是CAP理论,CAP理论是分布式系统的基石,CAP是Consistency(一致性)。 Availability(可用性
2018-06-20
Next 
Hexo和Github搭建个人博客 Hexo和Github搭建个人博客
​ 作为一个纯种程序猿,怎么能没有自己的博客呢。每每看大牛的博客时总是充满敬畏的同时,也想拥有一个属于自己的博客,一方面记录自己在技术上的问题和心得体会,另一方面也是记录着自己从菜鸟到大牛的成长经历。话不多说,下面来一起搭建一个自己的
2018-06-03
  TOC