字节码指令集

概述

Java字节码对于JVM就像汇编对于计算机,属于基本执行指令

java虚拟机有一个字节长度的、代表着某种特定操作含义的数字(称为操作码)以及紧随其后的0到多个代表此操作所需要参数(操作数)而构成。大多数的指令都不包含操作数,只有一个操作码

字节长度(0~255)限制了指令集死亡操作码总数不能超过256

执行流程

如果不考虑异常处理的话,那么Java虚拟机的解释器可以使用下面这个伪代码当做最基本的执行模型来理解

do{

自动计算PC寄存器的值加1;

根据Pc寄存器的指示位置,从字节码流中取出操作码;

if(字节码存在操作数){

​ 从字节码流中取出操作数;

​ 执行操作码所定义的操作;

}while(字节码长度>0);

加载和存储指令

操作数栈:存放计算的操作数栈和返回结果

  • 执行每一条指令之前,Java虚拟机要求该指令的操作数栈已被压入操作数栈中。在执行指令的时候,java虚拟机会将该指令所需要的操作数弹出,并且将指令的结果重新压入栈中

局部变量表:字节码程序将计算的结果缓存到局部变量区之中

  • jvm将局部变量区当成一个数组,依次存放this指针(仅非静态方法),所传入的参数,以及字节码中局部变量

局部变量压栈指令 load

局部变量压栈指令将给定的局部变量表中的数据压入操作数栈


                Snipaste_2023-05-28_10-33-08

//1.局部变量压栈指令
              public void load(int
                num, Object obj,long count,boolean
                flag,short[] arr) {
              System.out.println(num);
              System.out.println(obj);
              System.out.println(count);
              System.out.println(flag);
              System.out.println(arr);
              }

常量入栈指令

常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容不同,又可以分为const系列、push系列和ldc指令

const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里面

push系列:主要包括bipush和sipush。他们的主要区别是接收数据类型不同,bipush是接收8位整数作为参数,sipush接收16位整数,他们都将参数压入栈

  • 所以bipush的范围是-128~127
  • sipush的范围是 -32768~32767

ldc系列:

  • 如果以上指令都不能满足需求,那么可以使用万能的ldc系列,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的索引范围压入堆栈。
  • 类似的还有ldc_w,他接收两个8位参数,能支持的索引范围大于ldc
//2.常量入栈指令
              public void pushConstLdc() {
              int i = -1;
              int a = 5;
              int b = 6;
              int c = 127;
              int d = 128;
              int e = 32767;
              int f = 32768;
              }

总结

出栈装入局部变量表指令

算数运算符

++运算符

比较指令的说明

类型转换指令

  1. 类型转换指令可以将两种不同的数值类型进行相互转化
  2. 这些住转化操作一般用于实现用户带码中的显示类型转化操作,或者用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题

宽化类型转化

小范围类型向大范围类型转化的安全转化

  • int –> long、float或者double类型 对应的指令为:i2l, i2f, i2d
  • 从long–>float、double类型。对应的类型指令为:l2f, l2d
  • float–>double 对应的指令为: f2d

简化为: int–>long–>float–>double

精度损失问题

窄化类型处理

创建类和数组的实例的指令

可进一步分为创建指令,字段访问指令、数组操作指令、类型检查指令

创建指令

//1.创建指令
              public void newInstance() {
              Object obj = new Object();

              File file = new File("atguigu.avi");
              }

              public void newArray() {
              int[] intArray = new int[10];
              Object[] objArray = new Object[10];
              int[][] mintArray = new int[10][10];

              String[][] strArray = new String[10][];
              }
 0 new #2 <java/lang/Object>
 3 dup    // 复制一个引用   在存储的时候调用一个引用,在调用的时候同样会消耗一个引用
 4 invokespecial #1 <java/lang/Object.<init> : ()V>
 7 astore_1
 8 new #3 <java/io/File>
11 dup
12 ldc #4 <atguigu.avi>
14 invokespecial #5 <java/io/File.<init> : (Ljava/lang/String;)V>
17 astore_2
18 return

    
    
    
 0 bipush 10
 2 newarray 10 (int)
 4 astore_1
 5 bipush 10
 7 anewarray #2 <java/lang/Object>
10 astore_2
11 bipush 10
13 bipush 10
15 multianewarray #6 <[[I> dim 2
19 astore_3
20 bipush 10
22 anewarray #7 <[Ljava/lang/String;>   // 这里只是一维数组 原因:因为这种情况在堆中只开辟了一个长度为十的数组,里面每个元的类型strArry[]这在里没有做初始化,全部为null
25 astore 4
27 return

字段访问指令

getstatic和putstatic指令的主要区别在于getstatic用于获取静态字段的值,而putstatic用于设置静态字段的值。

getstatic指令的作用是将一个静态字段的值读取到操作数栈中,而putstatic指令的作用则是将一个操作数栈中的值写入到一个静态字段中去。

操作的是 值 !!!!

不同之处在于getstatic从静态字段中获取值并放入操作数栈顶,而putstatic从操作数栈顶取值并放入静态字段中。从这个角度来看,两者的操作方向是互相对称的。

public void setOrderId(){
              Order order = new Order();
              order.id = 1001;
              System.out.println(order.id);

              Order.name = "ORDER";
              System.out.println(Order.name);
              }
 0 new #11 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order>
 3 dup
 4 invokespecial #12 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order.<init> : ()V>
 7 astore_1
 8 aload_1
 9 sipush 1001
12 putfield #13 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order.id : I>
15 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
18 aload_1
19 getfield #13 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order.id : I>
22 invokevirtual #14 <java/io/PrintStream.println : (I)V>
25 ldc #15 <ORDER>
27 putstatic #16 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order.name : Ljava/lang/String;>  // 注:此时在操作数栈中只剩下了 ORDER 但此时因机构不需要将对象加载进来 ,应为静态变量对应的是类所以不需要对象的引用 以及不需要aload了
30 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
33 getstatic #16 <com/jvm/中篇/chapter02/src/com/atguigu/java/Order.name : Ljava/lang/String;>
36 invokevirtual #10 <java/io/PrintStream.println : (Ljava/lang/String;)V>
39 return

数组操作指令

注:这里store修改的就不是局部变量表了 而是将结果存储到堆空间中

//3.数组操作指令
              public void setArray() {
              int[] intArray = new int[10];
              intArray[3] = 20;
              System.out.println(intArray[1]);

              boolean[] arr = new boolean[10];
              arr[1] = true;
              }
 0 bipush 10
 2 newarray 10 (int)
 4 astore_1
 5 aload_1       // 数组引用 
 6 iconst_3      // 索引
 7 bipush 20     // 值
 9 iastore
10 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
13 aload_1      // 数组引用
14 iconst_1     // 索引
15 iaload
16 invokevirtual #14 <java/io/PrintStream.println : (I)V>
19 bipush 10
21 newarray 4 (boolean)
23 astore_2
24 aload_2      //数组引用
25 iconst_1     //索引
26 iconst_1     //值
27 bastore
28 return

类型检查指令

//4.类型检查指令
              public String checkCast(Object obj) {
              if (obj instanceof String) {
              // 判断obj是否是 String的一个实例
              return (String) obj;
              } else {
              return null;
              }
              }
 0 aload_1
 1 instanceof #17 <java/lang/String>
 4 ifeq 12 (+8)
 7 aload_1
 8 checkcast #17 <java/lang/String>
11 areturn
12 aconst_null
13 areturn

方法调用与返回指令

方法调用指令

静态类型绑定的方法不存在方法的重写

**invokespecial invokestatic **调用的方法中不存在方法的重写

eg: MethodInvokeReturnTest 以及 InterfaceMethodTest

方法返回指令

操作数栈管理指令

没有 变量调用(或者说 在整个方法周期中没有对该数据进行调用) 会将操作数栈中的数据pop出去

public void foo(){
              bar();
              }
              public long bar(){
              return 0;
              }
0 aload_0
1 invokevirtual #5 <com/jvm/中篇/chapter02/src/com/atguigu/java/StackOperateTest.bar : ()J>
4 pop2
5 return

控制转移指令

大体上分为:

  • 比较指令
  • 条件跳转指令
  • 比较条件跳转指令
  • 多条件分支跳转指令
  • 无条件跳转指令

比较指令

数值类型的数据才可以比较大小 double long float

boolean、引用数据类型不可以比较大小

条件跳转指令

//1.条件跳转指令
              public void compare1(){
              int a = 0;
              if(a != 0){
              a = 10;
              }else{
              a = 20;
              }
              }
 0 iconst_0
 1 istore_1
 2 iload_1
 3 ifeq 12 (+9)
 6 bipush 10
 8 istore_1
 9 goto 15 (+6)
12 bipush 20
14 istore_1
15 return
public void compare3() {
       int i1 = 10;
       long l1 = 20;
       System.out.println(i1 > l1);
   }
 0 bipush 10
 2 istore_1
 3 ldc2_w #6 <20>
 6 lstore_2
 7 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
10 iload_1
11 i2l      //先进行宽化处理
12 lload_2
13 lcmp     //比较指令进行比较  是-1
14 ifle 21 (+7)  // 条件符合进行跳转
17 iconst_1
18 goto 22 (+4)
21 iconst_0
22 invokevirtual #5 <java/io/PrintStream.println : (Z)V>
25 return
    
    
    注:在sysyem.out.print的时候传给的数字,按理说输出的应该也是数字 但是实际输出的是boolean 查看字节码后发现 <println : (Z)V>,sysyem.out.print使用的boolean类型的,所以输出的也是boolean类型

只有float double long才会进行比较指令和条件跳转指令相结合

如果本身是byte boolean char int 直接使用条件跳转指令(单个)

比较条件跳转指令

如果是两个int或者引用类型进行比较 则需要使用条件跳转指令

public void ifCompare2() {
              short s1 = 9;
              byte b1 = 10;
              System.out.println(s1 > b1);
              }
 0 bipush 9
 2 istore_1
 3 bipush 10
 5 istore_2
 6 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
 9 iload_1
10 iload_2
11 if_icmple 18 (+7)
14 iconst_1
15 goto 19 (+4)
18 iconst_0
19 invokevirtual #5 <java/io/PrintStream.println : (Z)V>
22 return

多条件分支跳转指令

//jdk7新特性:引入String类型
              public void swtich3(String season){
              switch(season){
              case "SPRING":break;
              case "SUMMER":break;
              case "AUTUMN":break;
              case "WINTER":break;
              }
              }
  0 aload_1
  1 astore_2
  2 iconst_m1
  3 istore_3
  4 aload_2
  5 invokevirtual #11 <java/lang/String.hashCode : ()I>
  8 lookupswitch 4
	-1842350579:  52 (+44)
	-1837878353:  66 (+58)
	-1734407483:  94 (+86)
	1941980694:  80 (+72)
	default:  105 (+97)
 52 aload_2
 53 ldc #12 <SPRING>
 55 invokevirtual #13 <java/lang/String.equals : (Ljava/lang/Object;)Z>
 58 ifeq 105 (+47)
 61 iconst_0
 62 istore_3
 63 goto 105 (+42)
 66 aload_2
 67 ldc #14 <SUMMER>
 69 invokevirtual #13 <java/lang/String.equals : (Ljava/lang/Object;)Z>
 72 ifeq 105 (+33)
 75 iconst_1
 76 istore_3
 77 goto 105 (+28)
 80 aload_2
 81 ldc #15 <AUTUMN>
 83 invokevirtual #13 <java/lang/String.equals : (Ljava/lang/Object;)Z>
 86 ifeq 105 (+19)
 89 iconst_2
 90 istore_3
 91 goto 105 (+14)
 94 aload_2
 95 ldc #16 <WINTER>
 97 invokevirtual #13 <java/lang/String.equals : (Ljava/lang/Object;)Z>
100 ifeq 105 (+5)
103 iconst_3
104 istore_3
105 iload_3
106 tableswitch 0 to 3
	0:  136 (+30)
	1:  139 (+33)
	2:  142 (+36)
	3:  145 (+39)
	default:  145 (+39)
136 goto 145 (+9)
139 goto 145 (+6)
142 goto 145 (+3)
145 return

无条件跳转指令

//4.无条件跳转指令
              public void whileInt() {
              int i = 0;
              while (i < 100) {
              String s = "atguigu.com";
              i++;
              }
              }
              
 0 iconst_0
 1 istore_1
 2 iload_1
 3 bipush 100
 5 if_icmpge 17 (+12)
 8 ldc #17 <atguigu.com>
10 astore_2
11 iinc 1 by 1
14 goto 2 (-12)
17 return

从字节码角度讲 for 和 while 没有任何区别 但从语法角度讲 while 和 for中i的作用域范围不同

异常处理指令

抛出异常指令

异常处理与异常表

public void tryCatch(){
              try{
              File file = new File("d:/hello.txt");
              FileInputStream fis = new FileInputStream(file);

              String info = "hello!";
              }catch (FileNotFoundException e) {
              e.printStackTrace();
              }
              catch(RuntimeException e){
              e.printStackTrace();
              }
              }

同步控制指令

方法级同步