深入理解JVM虚拟机第二十二篇:详解JVM当中与操作数栈相关的字节码指令

大神链接:作者有幸结识技术大神孙哥为好友,获益匪浅。现在把孙哥视频分享给大家。

孙哥链接:孙哥个人主页
作者简介:一个颜值99分,只比孙哥差一点的程序员
本专栏简介:话不多说,让我们一起干翻JVM

本文章简介:话不多说,让我们讲清楚JVM当中与操作数栈相关的字节码指令

文章目录

[一: 操作数栈字节码指令](#一: 操作数栈字节码指令)

1:编写源码

2:javap解释整理字节码

3:通过jclasslib查看字节码指令

二:字节码分析

1:最全字节码指令分析

2:面试题


一: 操作数栈字节码指令

1:编写源码

java 复制代码
public class OperandStackTest {
    public void testAndOperation(){
        byte i = 15;
        int j = 8;
        int k = i+j;
    }
}

2:javap解释整理字节码

想要查看字节码文件呢,我们有两种方式,第一种就是直接进行javap,第二种就是使用jclasslib进行查看,我们先使用第一种。

java 复制代码
PS D:\code\study\hadoop\shit\target\classes> javap -verbose .\OperandStackTest.class
Classfile /D:/code/study/hadoop/shit/target/classes/OperandStackTest.class
  Last modified 2023年11月9日; size 421 bytes
  SHA-256 checksum 487149a1edc4d19af0b1fe2369086c27c8765ff5e011491d1028d4f6cf1d9746
  Compiled from "OperandStackTest.java"
public class OperandStackTest
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // OperandStackTest
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #3.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // OperandStackTest
   #3 = Class              #21            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               LOperandStackTest;
  #11 = Utf8               testAndOperation
  #12 = Utf8               i
  #13 = Utf8               B
  #14 = Utf8               j
  #15 = Utf8               I
  #16 = Utf8               k
  #17 = Utf8               SourceFile
  #18 = Utf8               OperandStackTest.java
  #19 = NameAndType        #4:#5          // "<init>":()V
  #20 = Utf8               OperandStackTest
  #21 = Utf8               java/lang/Object
{
  public OperandStackTest();
    descriptor: ()V
    flags: (0x0001) 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
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LOperandStackTest;

  public void testAndOperation();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        15
         2: istore_1
         3: bipush        8
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 6
        line 6: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   LOperandStackTest;
            3       8     1     i   B
            6       5     2     j   I
           10       1     3     k   I
}
SourceFile: "OperandStackTest.java"

3:通过jclasslib查看字节码指令

首先进行recompile Java文件为字节码文件,然后我们在idea的view下找到这个:

show ByteCode with JclassLib:

最终显示结果如下:

二:字节码分析

1:最全字节码指令分析

java 复制代码
public class OperandStackTest {
    public void testAndOperation(){
        byte i = 15;
        int j = 8;
        int k = i+j;
    }
}
java 复制代码
 0 bipush 15
 2 istore_1
 3 bipush 8
 5 istore_2
 6 iload_1
 7 iload_2
 8 iadd
 9 istore_3
10 return

bipush将15这个值push到了操作数栈中,此时我们的操作数栈就有了第一个值。我们需要回顾一下:byte、short、char、boolean、int类型在声明之后往数组中进行存放的时候都会保存为int类型。也就是说虽然定义的是byte类型,但是存放到数组中就是int类型

栈帧在调用之初,栈帧被创建完成,其中的操作数栈和局部变量表是空的。PC寄存器中存放着第一条要执行的指令的地址。

istore_1将这个值从操作数栈放到了局部变量表中索引为1的位置,为什么不是0呢?因为这不是一个静态方法,索引为零的位置存放的是this。

此时的操作数栈就成了空,这是一个出栈的操作。过程中会修改PC寄存器中的索引值为下一条命令的索引值。

过程中会修改PC寄存器中的索引值为下一条命令的索引值。

同样的道理,8也会经过bipush和istore_2,然后最终的结果如下:

iload_1和iload_2命令会将变量中索引为1,2的数据取出来分别放到局部变量表中

最终的运行结果如下:

紧接着会进行一个iadd命令,这个命令呢会使数据进行出栈,然后相加。值得注意的是,字节码指令需要被翻译为机器指令,机器指令操作CPU进行相加。然后将结果23放到操作数栈当中。

运行结果如下:

最终,istore_3将这个值从操作数栈放到了局部变量表中索引为3的位置, 此时的操作数栈就成了空,这是一个出栈的操作。过程中会修改PC寄存器中的索引值为下一条命令的索引值。

最终的运行结果如下:

我们也注意到,局部变量表长度为4,操作数栈深度为2(看javap的结果),这也是与图中可以对应上的,唯一区别的是局部变量表中的因为篇幅原因,this的位置也就是索引为0的变量槽没有展示出来。

补充说明:

我们注意到bipush是将一个byte类型的数据push到操作数栈中基于int类型进行存储,还有sipush,这个字节码指令的含义是将short类型的数据push到操作数栈中基于int类型进行存储。

如果方法有返回值,那么最终的字节码指令将由return会变成ireturn。也就是将值做了一个返回,这个栈帧就结束了,另外一个调用此方法的栈帧会立即调用一个aload_x这样的一个操作,将上一个方法的返回值加载到此栈帧的操作数栈中。

java 复制代码
    public int getSum(){
        int m = 10;
        int n = 20;
        int k = m+n;
        return k;
    }

    public void testGetSum(){
        //aload_0获取上一个栈帧返回的结果,并保存在操作数栈中。
        int i = getSum();
        int j = 10;
    }

2:面试题

i++ 和 ++j的区别是什么?

此问题,后续我们在字节码文章中会跟大家进行探讨

相关推荐
小白的一叶扁舟10 小时前
深入剖析 JVM 内存模型
java·jvm·spring boot·架构
小池先生11 小时前
jvm_threads_live_threads 和 jvm_threads_states_threads 这两个指标之间存在一定的关系,但它们关注的维度不同
jvm
{⌐■_■}17 小时前
【GORM】事务,嵌套事务,保存点事务的使用,简单电商平台go案例
开发语言·jvm·后端·mysql·golang
Chancezhou19 小时前
【JVM】总结篇之GC性能优化案例
jvm·性能优化
Rverdoser20 小时前
多级缓存 JVM进程缓存
jvm·缓存
蚂蚁质量2 天前
什么是 Java 虚拟机(JVM)?
java·开发语言·jvm
日拱一卒无有尽, 功不唐捐终入海2 天前
Mybatis乐观锁使用
java·开发语言·jvm·mybatis
半旧5182 天前
cursor重构谷粒商城04——vagrant技术快速部署虚拟机
网络·计算机网络·重构·运维开发·虚拟机·vagrant·virtual box
做一个有信仰de人2 天前
【面试题】JVM部分[2025/1/13 ~ 2025/1/19]
java·jvm·面试
林汐的学习笔记2 天前
性能调优篇 四、JVM运行时参数
jvm