jvm(13) -- 字节码指令

小咪咪 2023-06-27 11:30 94阅读 0赞

文章目录

  • 字节码指令
    • 1.字节码指令简介
    • 2.字节码与数据类型
    • 3.加载和存储指令
      • 实操:
    • 4.运算指令
      • 实操:
    • 5.类型转换指令
      • 实例:
    • 6.对象创建与访问指令
      • 实操:
    • 7.操作数栈管理指令
    • 8.控制转移指令
      • 实操:
    • 9.方法调用指令和返回指令
      • 实操
    • 10.异常处理指令
      • 实操
    • 11.同步指令
      • 实操:

字节码指令

1.字节码指令简介

在这里插入图片描述

在这里插入图片描述

2.字节码与数据类型

在这里插入图片描述

​ 在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。例如,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据。这两条指令的操作在虚拟机内部可能会是由同一段代码来实现的,但在Class文件中他们必须拥有独自独立的操作码。

​ 对于大部分与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。也有一些指令的助记符中没有明确地指明操作类型的字母,如arraylength指令,他没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,如无条件跳转指令goto则是与数据类型无关的。

​ 由于Java虚拟机的操作码长度只有一个字节,所以包含了数据类型的操作码就为指令集的设计带来了很大的压力:如果每一种与数据类型相关的指令都支持Java虚拟机所有运行时数据类型的话,那指令的数量恐怕就会超出一个字节所能表示的数量范围了。因此,Java虚拟机的指令被设计成非完全独立的(Java虚拟机规范中把这种特性称为“Not Orthogonal”,即并非每种数据类型和每一种操作都有对应的指令)。有一些单独的指令可以在必要的时候用来将一些不支持的类型转换为可被支持的类型。

​ Java虚拟机所支持的与数据类型相关的字节码指令,通过使用数据类型列所代表的特殊字符替换opcode列的指令模板中的T,就可以得到一个具体的字节码指令。如果在表中指令模板与数据类型两列共同确定的格为空,则说明虚拟机不支持对这种数据类型执行这项操作。例如,load指令有操作int类型的iload,但是没有操作byte类型的同类指令。

3.加载和存储指令

在这里插入图片描述在这里插入图片描述

实操:

  1. public class HelloWorld {
  2. public int add(int a, int b){
  3. int c = a + b;
  4. return 1+1;
  5. }
  6. }

编译成class文件,然后执行:

javap -verbose HelloWorld.class

在这里插入图片描述

可以看到 红框为add方法,args为3,因为隐含一个this参数。

0: 将第2个参数int类型的a压入栈中

1:将第3个参数int类型的b压入栈中

2:执行 a+b

3:将a+b的结果c从操作数栈存储到局部变量表中

4: 对应的是1+1的这行代码,这里做了虚拟机优化,先把他给计算出来,得到了2,将这个常量2加载到操作数栈中。

5: irerurn 也就是返回一个int类型的数据。

4.运算指令

在这里插入图片描述
在这里插入图片描述

实操:

  1. public class HelloWorld {
  2. public int add(int a, int b) {
  3. int c = a + b;
  4. int d = a - b;
  5. int e = a * b;
  6. int f = a / b;
  7. int g = a % b;
  8. int h = c + d + e + f + g;
  9. return 1 + 1;
  10. }
  11. }

javap执行后的代码:

在这里插入图片描述

这里注意下:操作数栈是操作的顶层的2个参数。如下的代码会前面的数累加后再2个2个的add。

在这里插入图片描述

5.类型转换指令

在这里插入图片描述

实例:

  1. public class HelloWorld {
  2. public static int add(int a, int b) {
  3. int hour = 24;
  4. long mi = hour * 60 * 60 * 1000;
  5. long mic = hour * 60 * 60 * 1000 * 1000;
  6. System.out.println(mic/mi);
  7. return 1 + 1;
  8. }
  9. public static void main(String[] args) {
  10. add(1,2);
  11. }
  12. }

猜下运算结果:应该是1000

可是运行结果却是5:

在这里插入图片描述

why?

这里就需要从字节码类型转换指令看起。看下javap的字节码解析

在这里插入图片描述
上面的红色字体,不会超过,但是再乘以1000,肯定就超过int的长度了,变变小了。所以,值不是1000,而是5.

另看到 i2L就是int转long的转换,指令。

这个例子如果真想打印成1000.只要改一个地方就行。
在这里插入图片描述

再看下。字节码解析

在这里插入图片描述

已经没有转换了,代码中的数字大小不会出现超过数据类型长度问题了。,打印结果也是1000.

在这里插入图片描述

6.对象创建与访问指令

在这里插入图片描述

实操:

  1. public class HelloWorld {
  2. public static void main(String[] args) {
  3. //创建对象
  4. User user = new User();
  5. //创建对象数组
  6. User[] us = new User[10];
  7. //创建普通数组
  8. int[] as = new int[10];
  9. //设置对象属性值
  10. user.name = "hello";
  11. //访问对象属性
  12. String name = user.name;
  13. }
  14. }
  15. class User{
  16. String name;
  17. static int age;
  18. }

执行javap解析:

在这里插入图片描述

7.操作数栈管理指令

在这里插入图片描述

8.控制转移指令

在这里插入图片描述

实操:

  1. public class HelloWorld {
  2. public static void main(String[] args) {
  3. int a = 10;
  4. if (a > 10){
  5. System.out.println(" > 10 ");
  6. }else{
  7. System.out.println(" <= 10 ");
  8. }
  9. }
  10. }

在这里插入图片描述

javap解析代码:

在这里插入图片描述

分析:

0: 将常量10 压入操作数栈

2: 将10从操作数栈存储到局部变量表

3:将局部变量表的10再加载到操作数栈

4: 将括号内的10压入操作数栈桥

在这里插入图片描述

6: 控制转移指令if_icmpile进行比较,结果显示条件不成立,跳入第20行

在这里插入图片描述

20: 第20行调用out的静态类

在这里插入图片描述

23: 将<=10 这个常量压入操作数栈。

25:执行printlen方法,然后跳入第4行

28:返回。方法执行完毕。

如果a>10条件成立的话,执行完12,14,就会直接goto,goto指向28,退出方法。
在这里插入图片描述

9.方法调用指令和返回指令

在这里插入图片描述

在这里插入图片描述

实操

  1. public class HelloWorld {
  2. public static void main(String[] args) {
  3. Service s = new ServiceImpl();
  4. int res = s.add(1, 2);
  5. }
  6. }
  7. interface Service{
  8. int add(int a, int b);
  9. }
  10. class ServiceImpl implements Service{
  11. @Override
  12. public int add(int a, int b) {
  13. return a + b;
  14. }
  15. }

执行:

javap -verbose HelloWorld.class

在这里插入图片描述

执行:

javap -verbose ServiceImpl.class
在这里插入图片描述

10.异常处理指令

在这里插入图片描述

实操

  1. public class HelloWorld {
  2. public static void main(String[] args) {
  3. Service s = new ServiceImpl();
  4. int res = s.add(1, 2);
  5. try {
  6. int a = 1 / 0;
  7. }catch (Exception e){
  8. int b = 100;
  9. throw new RuntimeException("异常");
  10. }
  11. }
  12. }
  13. interface Service{
  14. int add(int a, int b);
  15. }
  16. class ServiceImpl implements Service{
  17. @Override
  18. public int add(int a, int b) {
  19. return a + b;
  20. }
  21. }

解析:

在这里插入图片描述

11.同步指令

在这里插入图片描述
在这里插入图片描述

实操:

  1. public class HelloWorld {
  2. public static void main(String[] args) throws Exception{
  3. synchronized (HelloWorld.class){
  4. new ServiceImpl().add(1, 2);
  5. }
  6. }
  7. }
  8. interface Service{
  9. int add(int a, int b);
  10. }
  11. class ServiceImpl implements Service{
  12. @Override
  13. public int add(int a, int b) {
  14. return a + b;
  15. }
  16. }

javap编译
在这里插入图片描述


发表评论

表情:
评论列表 (有 0 条评论,94人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JVM字节指令简介

    引言 众所周知,Java程序是运行在Java虚拟机上的,而这里的“虚拟”是对什么东西进行虚拟呢?答案当然就是对“实体”机进行虚拟啦,虚拟机可以看做是对实体机进行了进一步的封装

    相关 JVM字节指令

    加载和存储指令 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类指令包括如下内容。 将一个局部变量加载到操作栈: > iload、ilo