jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)

这个是这个系列的上一个文章(传送门):

jvm深入研究文档--程序执行专业户-虚拟机栈--jvm底层探索(2)_一单成的博客-CSDN博客

阿丹:

        在上一个文章中,主要探讨了虚拟机栈的主要组成成员以及中间的数据结构。那么这个文章就从代码的角度出发来看一看经过编译之后的代码,在jvm的影响下面到底是什么执行的,并且是怎么完成我们之前看着十分晦涩难懂的进站压栈出栈的。

小小的复习一下:

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第1张图片

在jvm的虚拟机栈中的组成为一个一个栈帧,在每个栈帧呢都是线程私有的,每个栈帧都包含了,局部变量表、操作数据表、动态链接和方法返回地址(值得注意的是方法返回地址在官方的说法下面一共有两个,一个是正常的方法出口,另一个是异常的方法出口)。

本文章的目标:

        我们来探讨和研究一下在这个底层到底是如何试进行数据的调用的以及如何进行数据的操作影响的在一个简单的方法中。

JVM字节码指令大全

实例代码:

public class Demo01 {

    private final int adn = 20;

    public int compute(int num){
        return adn + num;
    }

    public static void main(String[] args) {
        Demo01 demo01 = new Demo01();
        int res = demo01.compute(50);
        System.out.println(res);
    }
}

这段代码定义了一个名为Demo01的公共类。在这个类中,有一个私有的常量adn,其值为20。

接下来,定义了一个名为compute的公共方法,该方法接受一个整数参数num,并返回adnnum之和。

main方法中,创建了Demo01类的一个实例demo01,然后调用compute方法并传入50作为参数,将返回的结果赋值给变量res。最后,使用System.out.println打印出res的值。

这段代码很简单就是一个简简单单的数相加。

我们通过代码编译将这个代码转换成jvm编译过的代码。

我们来将这个java代码转换成对应的字节码指令集

我来描述一下在IDEA中的具体操作,如果动手能力强的小伙伴可以跟着我一起来尝试一下!

1、在idea中找到我们的代码

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第2张图片

右击 

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第3张图片

2、 使用代码将java代码编译为.class文件

java .\Demo01.java

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第4张图片

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第5张图片 3、使用javap来将生成的字节码输出到一个txt文件中

javap -c -v -p -l .\Demo01.class > "Demo01.txt"

对这个代码进行解释一下:

        -c对代码进行反编译

        -v输出附加信息,常量池等

        -l输出局部变量

        -p显示私有内容 

注意:还是在刚才的路径中和刚才的终端中的!

这个时候我们在本文件夹下面就能看到我们需要的文件内容了。

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第6张图片

Classfile /E:/desktop/stack/Demo01.class
  Last modified 2023-9-25; size 547 bytes
  MD5 checksum 291623c4dbf535dee20c1688119b9603
  Compiled from "Demo01.java"
public class Demo01
  minor version: 0
  major version: 61
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // Demo01.adn:I
   #8 = Class              #10            // Demo01
   #9 = NameAndType        #11:#12        // adn:I
  #10 = Utf8               Demo01
  #11 = Utf8               adn
  #12 = Utf8               I
  #13 = Methodref          #8.#3          // Demo01."":()V
  #14 = Methodref          #8.#15         // Demo01.compute:(I)I
  #15 = NameAndType        #16:#17        // compute:(I)I
  #16 = Utf8               compute
  #17 = Utf8               (I)I
  #18 = Fieldref           #19.#20        // java/lang/System.out:Ljava/io/PrintStream;
  #19 = Class              #21            // java/lang/System
  #20 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Methodref          #25.#26        // java/io/PrintStream.println:(I)V
  #25 = Class              #27            // java/io/PrintStream
  #26 = NameAndType        #28:#29        // println:(I)V
  #27 = Utf8               java/io/PrintStream
  #28 = Utf8               println
  #29 = Utf8               (I)V
  #30 = Utf8               ConstantValue
  #31 = Integer            20
  #32 = Utf8               Code
  #33 = Utf8               LineNumberTable
  #34 = Utf8               main
  #35 = Utf8               ([Ljava/lang/String;)V
  #36 = Utf8               SourceFile
  #37 = Utf8               Demo01.java
{
  private final int adn;
    descriptor: I
    flags: ACC_PRIVATE, ACC_FINAL
    ConstantValue: int 20

  public Demo01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: bipush        20
         7: putfield      #7                  // Field adn:I
        10: return
      LineNumberTable:
        line 1: 0
        line 3: 4

  public int compute(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: bipush        20
         2: iload_1
         3: iadd
         4: ireturn
      LineNumberTable:
        line 6: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #8                  // class Demo01
         3: dup
         4: invokespecial #13                 // Method "":()V
         7: astore_1
         8: aload_1
         9: bipush        50
        11: invokevirtual #14                 // Method compute:(I)I
        14: istore_2
        15: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_2
        19: invokevirtual #24                 // Method java/io/PrintStream.println:(I)V
        22: return
      LineNumberTable:
        line 10: 0
        line 11: 8
        line 12: 15
        line 13: 22
}
SourceFile: "Demo01.java"

现在我们开始正式解析一下这个字节码中的内容

jvm字节码大全

我在这里提供了一个连接是字节码的内容,需要的可以在这里拿取,并且下面的研究会用到。

我们能发现这个字节码虽然不是很认识但是发现中间类的结构大体还是没变化的。

 这里为类的定义

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第7张图片

这里为我们的常量

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第8张图片

 

这里为无参构造

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第9张图片

 这个就是我们自己写的计算方法

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第10张图片

 这里是我们写的main方法

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第11张图片

 解释:

        在每个方法中分了三个部分

1、code:对应的指令集

2、LineNumberTable:代码的行数和前面的代码的偏移量之间的关系

3、LocalVariableTable:这个就是局部变量表了

我们的重点就是我们的计算方法,我们要研究一下具体是如何使用虚拟机栈来完成操作的

 1、找到对应方法的指令集

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第12张图片

根据下面的code,看到第一条指令为bipush

我们根据JVM字节码指令查看一下

https://www.cnblogs.com/ageovb/p/15187314.html#%E5%8F%82%E8%80%83

 能看到它是将我们的常量20推送到栈顶,也就是压栈

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第13张图片

 第二条字节码指令为:iload_1

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第14张图片

 那么现在就引入了一个概念为局部变量,那么这个局部变量从哪里来的?

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第15张图片

我们知道在本地应用数据可以直接使用this来完成本地应用。

并且我们的常量在这个文件生成的时候就已经读取进来了,本地变量的表中的数据是根据数据的数据来完成装载的。那么就是说此时的本地变量为:

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第16张图片

常量可以使用this来表示并且引用。 那么这个我上面的图中的p1的名字实际上在我们的代码中应该是num!!!!

那此时的

就是将我们现在的赋值了的形参压入栈顶,我们将num复制为50

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第17张图片

 第三步字节码指令:iadd

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第18张图片

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第19张图片 jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第20张图片

第四步字节码指令: ireturn

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第21张图片

这个就是将数据拿出来放到主main方法中了。

那么在上述的方法中呢,只是对本地变量表读取。那么我们在mian方法中就有对于数据进行操作的。

在main方法中的数据操作本地变量表的指令

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第22张图片

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第23张图片

第一个指向是方法的调用,就是上面我们在讨论的方法,当获取到返回值之后将数据存到本地的第二个局部变量。因为现在栈中只剩下了一个计算过的数据。

在本地方法中的第一个位置是常量,那么在这个线程中的第二个数据就是我们刚才计算出来的那个了。

这个操作数栈和本地变量表就像是我们在草稿纸上操作数进行了计算然后将答案写在了本地方法栈上,并将草稿纸删掉。

动态链接是啥?:

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第24张图片

可以看到有些指令后面带了井号和数字,这个数字就是指向常量池中的引用。

引用的具体就是在后面的//注释的东西,就是引用的内容。

jvm深入研究文档-探索虚拟机栈底层代码到底是如何实现的?--jvm底层探索(3)_第25张图片

这里就是常量池,我们通过在-p之后添加-v的方法来将常量池进行打印!!! 

你可能感兴趣的:(JVM-java基础,jvm,linux,运维)