Yefei.Blog

个人日记 WIKI

用户工具

站点工具


java:jvm:bytecode

Java Bytecode

维基的介绍: https://zh.wikipedia.org/wiki/Java%E5%AD%97%E8%8A%82%E7%A0%81

字节码查看方式:

  1. 使用 JDK 自带的 javap -c 命令查看
  2. 使用更好的 GUI 工具 jclasslib
  3. Eclipse 中可以使用 Bytecode Outline 显示不太友好
  4. Eclipse 显示更友好的 Bytecode Visualizer 可惜无法安装

操作码助记符

助记符 类型
b byte
s short
i int
l long
f float
d double
c char
a reference

加载和存储指令

加载和存储指令用于将数据从栈帧(Stack Frame)的局部变量表和操作数栈之间来回传输

  • 局部变量 → 操作数栈: 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_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
  • 局部变量表访问索引: wide

运算指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶

  • 加法指令: iadd, ladd, fadd, dadd
  • 减法指令: isub, lsub, fsub, dsub
  • 乘法指令: imul, lmul, fmul, dmul
  • 除法指令: idiv, ldiv, fdiv, ddiv
  • 求余指令: irem, lrem, frem, drem
  • 取反指令: ineg, leng, fneg, dneg
  • 位移指令: ishl, ishr, iushr, lshl, lshr, lushr
  • 按位或指令: ior, lor
  • 按位与指令: iand, land
  • 按位异或指令: ixor, lxor
  • 局部变量自增指令: iinc
  • 比较指令: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
Java虚拟机没有明确规定整型数据溢出的情况,但规定了处理整型数据时,只有除法求余指令出现除数为0时会导致虚拟机抛出异常

类型转换指令

类型转换指令: i2b, i2c, i2s, l2i, f2i, f2l, d2l, d2f

类型转换可能会导致转换结果产生不同的正负号,不同数量级,转换过程可能会导致数值丢失精度
转换过程是仅仅丢弃最低位N个字节意外的内容(N是目标类型的数据长度)

对象创建与操作

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

  • 创建实例的指令: new
  • 创建数组的指令: newarray, anewarray, multianewarray
  • 访问字段指令: getfield, putfield, getstatic, putstatic
  • 数组元素 → 操作数栈: baload, caload, saload, iaload, laload, faload, daload, aaload
  • 操作数栈 → 数组元素: bastore, castore, castore, sastore, iastore, fastore, dastore, aastore
  • 取数组长度指令: arraylength
  • 检查实例类型指令: instanceof, checkcast

操作数栈管理指令

直接操作操作数栈的指令: pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap, 控制转移指令

控制转移指令

让JVM有条件或无条件从指定指令而不是控制转移指令的下一条指令继续执行程序

  • 条件分支: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull, if_cmpeq, if_icmpne, if_icmlt, if_icmpgt ……
  • 复合条件分支: tableswitch, lookupswitch
  • 无条件分支: goto, goto_w, jsr, jsr_w, ret
JVM中有专门的指令集处理int和reference类型的条件分支比较操作,为了可以无明显标示一个实体值是否是null,有专门的指令检测null 值。boolean类型和byte类型,char类型和short类型的条件分支比较操作,都使用int类型的比较指令完成,而 long,float,double条件分支比较操作,由相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件比较操作完成整个分支跳转。各种类型的比较都最终会转化为int类型的比较操作

方法调用和返回指令

  • invokevirtual: 调用对象的实例方法,根据对象的实际类型进行分派(虚拟机分派)
  • invokeinterface: 调用接口方法,在运行时搜索一个实现这个接口方法的对象,找出合适的方法进行调用
  • invokespecial: 调用需要特殊处理的实例方法,包括实例初始化方法,私有方法和父类方法
  • invokestatic: 调用类方法(static)
方法返回指令是根据返回值的类型区分的,包括ireturn(返回值是boolean,byte,char,short和 int),lreturn,freturn,drturn和areturn,另外一个return供void方法,实例初始化方法,类和接口的类初始化i 方法使用

同步

JVM支持方法级同步和方法内部一段指令序列同步,这两种都是通过moniter实现的

方法级的同步是隐式的,无需通过字节码指令来控制,它实现在方法调用和返回操作中。虚拟机从方法常量池中的方法标结构中的 ACC_SYNCHRONIZED标志区分是否是同步方法。方法调用时,调用指令会检查该标志是否被设置,若设置,执行线程持有moniter,然后执行方法,最后完成方法时释放moniter

同步一段指令集序列,通常由synchronized块标示,JVM指令集中有monitorenter和monitorexit来支持synchronized语义

结构化锁定是指方法调用期间每一个monitor退出都与前面monitor进入相匹配的情形。JVM通过以下两条规则来保证结结构化锁成立(T代表一线程,M代表一个monitor):

  • T在方法执行时持有M的次数必须与T在方法完成时释放的M次数相等
  • 任何时刻都不会出现T释放M的次数比T持有M的次数多的情况
java/jvm/bytecode.txt · 最后更改: 2016/10/20 14:19 由 yefei