JVM–基础–22–字节码指令



1、字节码简介

  1. Java字节码由操作码和操作数组成。
  2. 操作码:1个字节长度,代表某种特定操作含义的数字
  3. 操作数:零至多个代表此操作码所需参数

2、字节码与数据类型

  1. 在字节码指令集中,大多数指令都有操作所对应的数据类型信息,比如iload表示从局部变量表中加载int型的数据到操作栈中.
  2. 除了long和double类型外,每个变量都占局部变量区中的一个变量槽(slot),而long及double会占用两个连续的变量槽。
  3. 大多数对于boolean、byte、short和char类型数据的操作,都使用相应的int类型作为运算类型。
  4. 每种数据类型和操作都有对应的指令,有一些指令可以在必要的时候将一些不被支持的数据类型转换为被支持的数据类型。

2.1、数据类型表

  1. 以数据类型为列,
  2. 以操作指令为行
  3. 其中为空的项即说明虚拟机不支持对这种数据类型进行这项操作。

java 字节码ldc java字节码指令详解_开发语言

3、加载和存储指令

3.1、指令

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

3.2、测试

public class Hello{
    
    public int add(int a,int b)
    {
        int c=a+b;
        int d=1;
        int f=c+d;
        return f;
    }
}

3.3、解析

F:\>javap -v Hello.class
Classfile /F:/Hello.class
  Last modified 2019-7-23; size 264 bytes
  MD5 checksum d09a5fcab945489db588d5e862fad676
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#12         //  java/lang/Object."<init>":()V
   #2 = Class              #13            //  Hello
   #3 = Class              #14            //  java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               add
   #9 = Utf8               (II)I
  #10 = Utf8               SourceFile
  #11 = Utf8               Hello.java
  #12 = NameAndType        #4:#5          //  "<init>":()V
  #13 = Utf8               Hello
  #14 = Utf8               java/lang/Object
{
  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int add(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=6, args_size=3
         0: iload_1     //将一个局部变量a加载到操作栈
         1: iload_2        //将一个局部变量b加载到操作栈
         2: iadd        //a+b
         3: istore_3    //将a+b的数值从操作数栈存储到局部变量表c中
         4: iconst_1    //定义常量d=1
         5: istore        4//将d的数值从操作数栈存储到局部变量表中d
         7: iload_3            //将一个局部变量c加载到操作栈
         8: iload         4    //将一个局部变量d加载到操作栈
        10: iadd            //c+d
        11: istore        5 //将c+d的数值从操作数栈存储到局部变量表
        13: iload         5 //将一个局部变量(c+d)加载到操作栈
        15: ireturn            //返回数值
      LineNumberTable:
        line 5: 0
        line 6: 4
        line 7: 7
        line 8: 13
}

4、运算指令

用于对操作数栈上的值进行某种特定的运算。(这里不多说看上面的测试代码)

加法运算:iadd,ladd,fadd,dadd。
减法运算:isub,lsub,fsub,dsub。
乘法运算:imul,lmul,fmul,dmul。
除法运算:idiv,ldiv,fdiv,ddiv。
求余指令:irem,lrem,frem,drem。
取反指令:imeg,lmeg,fmeg,dmeg。
位移指令:ishl,ishr,iushr,lshl,lshr,lushr。
按位或指令:ior,lor。
按位与指令:iand,land。
按位异或指令:ixor,lxor。
局部变量自增指令:iinc。
比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp。
	注:只有在除法指令(idiv,ldiv)和求余指令(irem,lrem)当出现除数为零时会导致虚拟机抛出AirtmeticException异常,其余整形和浮点型运算场景都不会抛出异常

5、类型转换指令

  1. 可以将两种不同数值类型进行相互转换。
  2. 虚拟机天然支持基本数据类型的宽化类型转换,例如int到long、flost、double等。
  3. 对于窄化数据类型转化则必须用显示的转换指令。

5.1、显示的转换指令

i2b(int -> boolean)
i2c(int -> char)
i2s(int -> short)
l2i(long -> int)
f2i(float -> int)
f2l(float -> long)
d2i(double -> int)
d2l(double -> long)
d2f(double -> float)

5.2、注意点

  1. int/long 类型窄化转换为整数类型T时,转换过程为丢弃除最低位N(T的数据类型长度)个字节以外的内容。
  2. 浮点值窄化转换为整数类型T(int/long)时
    if(浮点值==NaN){
        result = 0;
    }else{
        value = [浮点值];  //向下取整
        if(T.min <= value <= T.max){    //value在T的表示范围内
            result = value;
        }else{
            if(value > 0) result = T.max;
            if(value < 0) result = T.min;
        }
    }

5.3、测试代码

public class Hello{
    
    public long  add()
    {
        int a=11;
        long  b=20;
        long  c=a+b;
        return c;
    }
 
}

5.4、解析

F:\>javap -v Hello.class
Classfile /F:/Hello.class
  Last modified 2019-7-23; size 271 bytes
  MD5 checksum dc4f5dbebf95b9a1357abb2a994824b8
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         //  java/lang/Object."<init>":()V
   #2 = Long               20l
   #4 = Class              #15            //  Hello
   #5 = Class              #16            //  java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               add
  #11 = Utf8               ()J
  #12 = Utf8               SourceFile
  #13 = Utf8               Hello.java
  #14 = NameAndType        #6:#7          //  "<init>":()V
  #15 = Utf8               Hello
  #16 = Utf8               java/lang/Object
{
  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public long add();
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=6, args_size=1
         0: bipush        11       //将一个常量a=11加载到操作数栈
         2: istore_1                 //将一个数值11从操作数栈存储到局部变量表
         3: ldc2_w        #2        // long 20l   一个常量b=20加载到操作数栈
         6: lstore_2                 //将一个数值b=20从操作数栈存储到局部变量表
         7: iload_1            //将一个局部变量a=11加载到操作栈
         8: i2l                //操作栈 a=11从int 转换long
         9: lload_2            //将一个局部变量b=20加载到操作栈
        10: ladd            //a+b
        11: lstore        4 //将一个数值(a+b)从操作数栈存储到局部变量表
        13: lload         4  //将一个局部变量(a+b)加载到操作栈
        15: lreturn            //返回
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 7
        line 8: 13
}

6、对象创建与访问指令

6.1、指令

创建类实例的指令:
	new
创建数组的指令:
	newarray
	anewarray
	multianewarray
访问类字段(static字段)和实例字段(非static字段)的指令
	getfield
	putfield
	getstatic
	putstatic
将一个数组元素加载到操作数栈的指令:
	baload
	caload
	saload
	iaload
	faload
	daload
	aaload
将一个操作数栈的值存储到数组元素中的指令
	bastore
	castore
	iastore
	sastore
	fastore
	fastore
	dastore,aastore
取数组长度的指令:
	arraylength
检查类实例类型的指令:
	instanceof
	checkcast

6.2、代码

public class Hello{
    
    private int age;
    
    public static void  main(String[] args)
    {
        
         Hello hello=new Hello();
         hello.setAge(11);
         int c=hello.getAge();
        
        
    }
    
    public int getAge(){
        return age;
    }
    public void  setAge(int age){
        this.age=age;
    }
    
}

6.3、解析

F:\>javap -v Hello.class
Classfile /F:/Hello.class
  Last modified 2019-7-23; size 461 bytes
  MD5 checksum 405d9ef6996eb40b92f9991ad03c76db
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#22         //  java/lang/Object."<init>":()V
   #2 = Class              #23            //  Hello
   #3 = Methodref          #2.#22         //  Hello."<init>":()V
   #4 = Methodref          #2.#24         //  Hello.setAge:(I)V
   #5 = Methodref          #2.#25         //  Hello.getAge:()I
   #6 = Fieldref           #2.#26         //  Hello.age:I
   #7 = Class              #27            //  java/lang/Object
   #8 = Utf8               age
   #9 = Utf8               I
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               getAge
  #17 = Utf8               ()I
  #18 = Utf8               setAge
  #19 = Utf8               (I)V
  #20 = Utf8               SourceFile
  #21 = Utf8               Hello.java
  #22 = NameAndType        #10:#11        //  "<init>":()V
  #23 = Utf8               Hello
  #24 = NameAndType        #18:#19        //  setAge:(I)V
  #25 = NameAndType        #16:#17        //  getAge:()I
  #26 = NameAndType        #8:#9          //  age:I
  #27 = Utf8               java/lang/Object
{
  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class Hello  创建类实例的指令
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        11
        11: invokevirtual #4                  // Method setAge:(I)V
        14: aload_1
        15: invokevirtual #5                  // Method getAge:()I
        18: istore_2
        19: return
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 14
        line 13: 19

  public int getAge();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #6                  // Field age:I  访问类字段(static字段)和实例字段(非static字段)的指令
         4: ireturn
      LineNumberTable:
        line 16: 0

  public void setAge(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #6                  // Field age:I  访问类字段(static字段)和实例字段(非static字段)的指令
         5: return
      LineNumberTable:
        line 19: 0
        line 20: 5
}

7、操作数栈管理指令

将一个操作数栈的栈顶一个或两个元素出栈:
	pop
	pop2
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:
	dup
	dup2
	dup_x1
	dup2_x1
	dup_x2
	dup2_x2
将栈顶端的两个数值交换
	swap

8、控制转移指令

可以让Java虚拟机有条件或者无条件的从指定的位置而不是控制转移指令的下一条指令继续执行程序。

8.1、指令

条件分支:
	ifeq
	ifit
	ifle
	ifgt
	ifnull
	ifnonnull
	if_icmpeq
	if_icmpne
	if_icmplt
	if_icmpgt
	if_icmple
	if_icmpge
	if_acmpeq
	if_acmpne
复合条件分支:
	tableswitch
	lookupswitch
无条件分支:
	gosto
	goto_w
	jsr
	jsr_w
	ret

8.2、代码

public class Hello{
    
    public static void  main(String[] args)
    {
         int a=11;
         int b=0;
         if(a>0){
             b=2;
         }else{
             b=1;
         }
    }
}

8.3、解析

F:\>javap -v Hello.class
Classfile /F:/Hello.class
  Last modified 2019-7-23; size 321 bytes
  MD5 checksum 344b92a34b16863c6264573b7620b263
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#13         //  java/lang/Object."<init>":()V
   #2 = Class              #14            //  Hello
   #3 = Class              #15            //  java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               main
   #9 = Utf8               ([Ljava/lang/String;)V
  #10 = Utf8               StackMapTable
  #11 = Utf8               SourceFile
  #12 = Utf8               Hello.java
  #13 = NameAndType        #4:#5          //  "<init>":()V
  #14 = Utf8               Hello
  #15 = Utf8               java/lang/Object
{
  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: bipush        11     //常量11加载到操作数栈
         2: istore_1            //将一个数值11从操作数栈存储到局部变量表
         3: iconst_0            //常量0加载到操作数栈
         4: istore_2                //将一个数值0从操作数栈存储到局部变量表
         5: iload_1                //将一个局部变量11加载到操作栈
         6: ifle          14   //11 小于 ,跳转14,否则继续走下去
         9: iconst_2            //常量2加载到操作数栈
        10: istore_2            //将一个数值2从操作数栈存储到局部变量表
        11: goto          16   //走到16行
        14: iconst_1
        15: istore_2
        16: return      //结束
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 5
        line 8: 9
        line 10: 14
        line 12: 16
      StackMapTable: number_of_entries = 2
           frame_type = 253 /* append */
             offset_delta = 14
        locals = [ int, int ]
           frame_type = 1 /* same */

}

9、方法调用和返回指令

9.1、指令

invokevirtua
	用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)。
invokeinterface
	用于调用接口方法,它在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokespecial
	用于调用一些需要特殊处理的实例方法,包括实例的初始化方法,私有方法和父类方法。
invokestatic
	用于调用类方法(static方法)
invokedynamic
	用于运行时动态解析出调用点限定符所应用的方法,并执行该方法。(前面的分派逻辑都固化在虚拟机内部,而该指令的分派逻辑是由用户自定义)。
方法返回指令:
	ireture(返回类型是int,short,byte,char,boolean时)
	lreturn
	freturn
	dreturn
	areturn
	还有一条return供void方法、实例/类/接口的初始化方法使用

9.2、代码

public class Hello{
    
    private int age;
    
    public static void  main(String[] args)
    {
        
         Hello hello=new Hello();
         hello.setAge(11);
         int c=hello.getAge();
        
        
    }
    
    public int getAge(){
        return age;
    }
    public void  setAge(int age){
        this.age=age;
    }
    
}

9.3、解析

F:\>javap -v Hello.class
Classfile /F:/Hello.class
  Last modified 2019-7-23; size 461 bytes
  MD5 checksum 405d9ef6996eb40b92f9991ad03c76db
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#22         //  java/lang/Object."<init>":()V
   #2 = Class              #23            //  Hello
   #3 = Methodref          #2.#22         //  Hello."<init>":()V
   #4 = Methodref          #2.#24         //  Hello.setAge:(I)V
   #5 = Methodref          #2.#25         //  Hello.getAge:()I
   #6 = Fieldref           #2.#26         //  Hello.age:I
   #7 = Class              #27            //  java/lang/Object
   #8 = Utf8               age
   #9 = Utf8               I
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               getAge
  #17 = Utf8               ()I
  #18 = Utf8               setAge
  #19 = Utf8               (I)V
  #20 = Utf8               SourceFile
  #21 = Utf8               Hello.java
  #22 = NameAndType        #10:#11        //  "<init>":()V
  #23 = Utf8               Hello
  #24 = NameAndType        #18:#19        //  setAge:(I)V
  #25 = NameAndType        #16:#17        //  getAge:()I
  #26 = NameAndType        #8:#9          //  age:I
  #27 = Utf8               java/lang/Object
{
  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class Hello  创建类实例的指令
         3: dup
         4: invokespecial #3                  // Method "<init>":()V  实例的初始化方法
         7: astore_1
         8: aload_1
         9: bipush        11
        11: invokevirtual #4                  // Method setAge:(I)V   用于调用对象的实例方法
        14: aload_1
        15: invokevirtual #5                  // Method getAge:()I    用于调用对象的实例方法
        18: istore_2
        19: return             、//方法返回指令
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 14
        line 13: 19

  public int getAge();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #6                  // Field age:I  访问类字段(static字段)和实例字段(非static字段)的指令
         4: ireturn
      LineNumberTable:
        line 16: 0

  public void setAge(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #6                  // Field age:I  访问类字段(static字段)和实例字段(非static字段)的指令
         5: return
      LineNumberTable:
        line 19: 0
        line 20: 5
}

10、异常处理指令

  1. 显式抛出异常指令:athrow
  2. 在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现,除了用throw语句显式抛出异常情况之外,Java虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。

10.1、测试

public class Hello{
 
    public static void  main(String[] args)
    {
        
         throw new RuntimeException("我是异常");
    
    }
}

java 字节码ldc java字节码指令详解_开发语言_02

11、同步指令

Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。

11.1、方法级同步

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

11.2、方法内部一段指令序列的同步

同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持。

11.3、测试

java 字节码ldc java字节码指令详解_java 字节码ldc_03

java 字节码ldc java字节码指令详解_java 字节码ldc_04