欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

程序员文章站 2022-05-12 14:19:53
...

字节码指令

构造方法 的字节码指令

2a b7 00 01 b1

那这些具体代表着什么含义?我们可以在官方文档当中进行查看不同的数值代替什么不同的含义:以2a为例,在这里都是十六进制的数,在文档当中查找0x2a,其余指令同理:官网文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

  1. 2a => aload_0 加载 slot 0 的局部变量,即 this,做为下面的 invokespecial 构造方法调用的参数
  2. b7 => invokespecial 预备调用构造方法,哪个方法呢?
  3. 00 01 引用常量池中 #1 项,即【 Method java/lang/Object."<init>":()V 】 构造方法,V表示无返回值
  4. b1 表示返回

第二个就是主方法的字节码指令 public static void main(java.lang.String[]);

b2 00 02 12 03 b6 00 04 b1

和上方同理,在官网文档当中查找字节码信息

  1. b2 => getstatic 用来加载静态变量,哪个静态变量呢?
  2. 00 02 引用常量池中 #2 项,即【Field java/lang/System.out:Ljava/io/PrintStream;】
  3. 12 => ldc 加载参数,哪个参数呢?
  4. 03 引用常量池中 #3 项,即 【String hello world】
  5. b6 => invokevirtual 预备调用成员方法,哪个方法呢?
  6. 00 04 引用常量池中 #4 项,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
  7. b1 表示返回

javap工具

对class文件进行反编译,使用命令javap -v class文件名

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
字节码指令 和 操作数栈、常量池的关系

使用一段简单的java代码进行测试

public class relationship {
    public static void main(String[] args) {
        int a= 10;
        int b=Short.MAX_VALUE+1;
        int c= a+b;
        System.out.print(c);
    }
}

运行java代码得到class文件,使用javap指令对class文件进行反编译,在这里第三行的32768这个数只,在这里只有Short所含的数字大小才会跟随变量进行保存,其余大的数值都会保存在常量池当中。

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
随后会将常量池载入到运行时常量池

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
方法字节码载入方法区

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
main 线程开始运行,分配栈帧内存

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
到这里就运行main方法当中的指令,在对class反编译后,查看对应的code

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

1 :bipush 10
  • 将一个 byte 压入操作数栈(其长度会补齐 4 个字节)
  • sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
  • ldc 将一个 int 压入操作数栈
  • ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)
  • 这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

2: istore_1

将操作数栈顶数据弹出,存入局部变量表的 slot 1

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

3: ldc #3

从常量池加载 #3 数据到操作数栈
Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好的

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

4 : istore_2

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

5:iload_1 和 iload_2

读取数据到操作栈上

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

6:iadd

执行相加算法

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

7:istore_3

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

8:getstatic #4

在常量池当中找到成员变量的引用。

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
读取之后放入操作栈当中。

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

9:iload3

读取第3个栈当中的数据

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

10:invokevirtual #5
  • 找到常量池 #5 项
  • 定位到方法区 java/io/PrintStream.println:(I)V 方法
  • 生成新的栈帧(分配 locals、stack等)
  • 传递参数,执行新栈帧中的字节码

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
执行完毕,弹出栈帧,清除 main 操作数栈内容

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
return :完成 main 方法调用,弹出 main 栈帧,程序结束。

案例分析 a- -

一个简单的代码段进行测试:使用字节码的技术对这段代码进行分析:

public class interview {
    public static void main(String[] args) {
        int a=10;
        int b = a++ + ++a + a--;
        System.out.print(a);
        System.out.print(b);
    }
}

还是一样,先使用javap对class文件进行反编译,其中:

  • iinc 指令是直接在局部变量 slot 上进行运算
  • a++ 是先执行 iload 还是 先执行 iinc
  • ++a 是先执行 iinc 还是 先执行 iload

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

  1. bipush 10 把10放进操作栈当中
  2. istore_1 把10弹出,放进第一个栈帧当中
  3. iload_1 把第一个栈帧的数据读取出来放入操作栈当中
  4. linc 进行自增
  5. linc load先自增再load a=12
    JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
  6. 先load再自减
    JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
  7. 最后弹出数据的结果 a=11 b=34

条件判断指令

如下表所示:

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)

  • byte , short , char都会按int比较,因为操作数栈都是4字节
  • goto用来进行跳转到指定行号的字节码

使用一个简单的判断的java代码进行测试

public class if_demo {
    public static void main(String[] args) {
        int a = 0;
        if (a == 0) {
            a = 20;
        } else {
            a = 10;
        }
    }
}

之后还是使用javap对class文件进行反编译:

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
循环指令控制

使用一段简单的java循环代码进行测试,在这里while循环和do while同理。

public class while_demo {
    public static void main(String[] args) {
        int a = 0;
        while (a < 10) {
            a++;
        }
    }
}

使用javap对class进行反编译,查看code编码

JVM 字节码(字节码指令 和 操作数栈、常量池的关系)
案例分析(循环)

查看以下代码段:判断x最后的值

public class case_analysis {
    public static void main(String[] args) {
        int i = 0;
        int x = 0;
        while (i < 10) {
            x = x++;
            i++;
        }
        System.out.println(x);
    }
}

答案是0,在这里的x=x++;这条命令在字节码当中分为了两个阶段,先进行load在进行ilic,可以发现,在这里先把x的值加载到操作栈当中,而在栈帧当中进行自加,再把操作栈当中的x值覆盖栈帧当中的值,也就是一直是把x=0这个值对栈帧当中自增的值进行覆盖,所以x始终为0;