关于后端:深入浅出JVM九之字节码指令上篇

95次阅读

共计 3666 个字符,预计需要花费 10 分钟才能阅读完成。

本篇文章次要围绕字节码的指令,深入浅出的解析各种类型字节码指令,如:加载存储、算术、类型转换、对象创立与拜访、办法调用与返回、管制本义、异样解决、同步等

因为字节码指令品种太多,本文作为上篇概述加载存储、算术、类型转换的字节码指令

应用 idea 中的插件 jclasslib 查看编译后的字节码指令

字节码指令集

大部分指令先 以 i(int)、l(long)、f(float)、d(double)、a(援用)结尾

其中 byte、char、short、boolean 在 hotspot 中都是转成 int 去执行(应用 int 类型的字节码指令)

字节码指令大抵分为:

  1. 加载与存储指令
  2. 算术指令
  3. 类型转换指令
  4. 对象创立与拜访指令
  5. 办法调用与返回指令
  6. 操作数栈治理指令
  7. 管制本义指令
  8. 异样解决指令
  9. 同步控制指令

在 hotspot 中每个办法对应的一组 字节码指令

这组 字节码指令在该办法所对应的栈帧中的局部变量表和操作数栈上进行操作

字节码指令蕴含字节码操作指令 和 操作数(操作数可能是在局部变量表上也可能在常量池中还可能就是常数)

加载与存储指令

加载

加载指令就是把操作数加载到操作数栈中(能够从局部变量表,常量池中加载到操作数栈)

  • 局部变量表加载指令

    • i/l/f/d/aload 前面跟的操作数就是要去局部变量表的哪个槽取值
    • iload_0: 去局部变量表 0 号槽取出 int 类型值
  • 常量加载指令

    • 能够依据加载的常量范畴分为三种(从小到大)const < push < ldc

存储

存储指令就是将操作数栈顶元素出栈后,存储到局部变量表的某个槽中

  • 存储指令

    • i/l/f/d/astore 前面跟的操作数就是要存到局部变量表的哪个槽
    • istore_1:出栈栈顶 int 类型的元素保留到局部变量表的 1 号槽

留神: 编译时就晓得了局部变量表应该有多少槽的地位 和 操作数栈的最大深度(为节俭空间,局部变量槽还会复用)

从常量池加载 100 存储到局部变量表 1 号槽,从常量池加载 200 存储到局部变量表 2 号槽(其中局部变量表 0 号槽存储 this)

算术指令

算术指令将操作数栈中的俩个栈顶元素出栈作运算再将运算后果入栈

应用的是后缀表达式(逆波兰表达式),比方 3 4 + => 3 + 4

留神

  1. 当除数是 0 时会抛出 ArithmeticException 异样
  2. 浮点数转整数向 0 取整
  3. 浮点数计算精度失落
  4. Infinity 计算结果无穷大
  5. Nan 计算结果不确定计算值
     public void test1() {
         double d1 = 10 / 0.0;
         //Infinity
         System.out.println(d1);
 ​
         double d2 = 0.0 / 0.0;
         //NaN
         System.out.println(d2);
 ​
         // 向 0 取整模式: 浮点数转整数
         //5
         System.out.println((int) 5.9);
         //-5
         System.out.println((int) -5.9);
 ​
 ​
         // 向最靠近数舍入模式:浮点数运算
         //0.060000000000000005
         System.out.println(0.05+0.01);
 ​
         // 抛出 ArithmeticException: / by zero 异样
         System.out.println(1/0);
     }

类型转换指令

类型转换指令能够分为 宽化类型转换 窄化类型转换(对应根本类型的非强制转换和强制转换)

宽化类型转换

小范畴向大范畴转换

  • int -> long -> float -> double

    • i2li2fi2d
    • l2fl2d
    • f2d

byte、short、char 应用 int 类型的指令

留神: long 转换为 float 或 double 时可能产生精度失落

     public void test2(){
         long l1 =  123412345L;
         long l2 =  1234567891234567899L;
 ​
         float f1 = l1;
         // 后果: 1.23412344E8 => 123412344
         //                l1 =  123412345L
         System.out.println(f1);
 ​
         double d1 = l2;
         // 后果: 1.23456789123456794E18 => 1234567891234567940
         //                          l2 =  1234567891234567899L
         System.out.println(d1);
     }

窄化类型转换

大范畴向小范畴转换

  • int->byte、char、short: i2bi2ci2s
  • long->int: l2i
  • float->long、int: f2lf2i
  • double->float、long、int: d2fd2ld2i

如果 long,float,double 要转换为 byte,char,short 能够先转为 int 再转为绝对应类型

窄化类型转换会产生精度失落

NaN 和 Infinity 的非凡状况:

     public void test3(){
         double d1 = Double.NaN;
         double d2 = Double.POSITIVE_INFINITY;
 ​
         int i1 = (int) d1;
         int i2 = (int) d2;
         //0
         System.out.println(i1);
         //true
         System.out.println(i2==Integer.MAX_VALUE);
 ​
         long l1 = (long) d1;
         long l2 = (long) d2;
         //0
         System.out.println(l1);
         //true
         System.out.println(l2==Long.MAX_VALUE);
 ​
         float f1 = (float) d1;
         float f2 = (float) d2;
         //NaN
         System.out.println(f1);
         //Infinity
         System.out.println(f2);
     }

NaN 转为整型会变成 0

正无穷或负无穷转为整型会变成那个类型的最大值或最小值

对象创立与拜访指令

对象创立与拜访指令: 创立指令、字段拜访指令、数组操作指令、类型查看指令

创立指令

new: 创立实例

newarray: 创立一维根本类型数组

anewarray: 创立一维援用类型数组

multianewarray: 创立多维数组

留神: 这里的创立能够了解为分配内存, 当多维数组只调配了一维数组时应用的是anewarray

字段拜访指令

getstatic: 对动态字段进行读操作

putstatic: 对动态字段进行写操作

getfield: 对实例字段进行读操作

putfield: 对实例字段进行写操作

读操作: 把要进行读操作的字段入栈

写操作: 把要写操作的值出栈再写到对应的字段

数组操作指令

  • b/c/s/i/l/f/d/a aload : 示意将数组中某索引元素入栈 (读)

    • 须要的参数从栈顶顺次向下: 索引地位、数组援用
  • b/c/s/i/l/f/d/a astore: 示意将某值出栈并写入数组某索引元素 (写)

    • 须要的参数从栈顶顺次向下: 要写入的值、索引地位、数组援用

留神: b 结尾的指令对 byte 和 boolean 通用

  • arraylength: 先将数组援用出栈再将取得的数组长度入栈

类型查看指令

instanceof: 判断某对象是否为某类的实例

checkcast: 查看援用类型是否能够强制转换

总结

因为字节码指令品种多篇幅长,将会分为上、下篇来深入浅出解析字节码指令,本篇作为上篇深入浅出的解析字节码指令介绍、加载存储指令、算术指令、类型转换指令以及对象创立与拜访指令

字节码指令大部分以 i、l、f、d、a 结尾,别离含意对应 int、long、float、double、援用,其中 byte、char、short、boolean 会转换为 int 来执行

字节码指令分为字节码操作指令和须要操作的数据,数据可能来源于局部变量表或常量池

加载指令从局部变量表或者常量池中加载数据,存储指令将存储到对应局部变量表的槽中,实例办法的局部变量表的 0 号槽罕用来存储 this,如果办法中变量是部分存在的还可能会复用槽

算术指令为各种类型和各种算术提供算术规定,在操作数栈中应用后缀表达式对操作数进行算术

类型转换分为宽化与窄化,都可能存在精度损失

对象创立与拜访指令中蕴含创建对象,拜访实例、动态字段,操作数组,类型查看等指令

最初(一键三连求求拉~)

本篇文章笔记以及案例被支出 gitee-StudyJava、github-StudyJava 感兴趣的同学能够 stat 下继续关注喔 \~

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下 \~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0