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


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

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

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

    文章目录

    一: 操作数栈字节码指令

    1:编写源码

    2:javap解释整理字节码

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

    二:字节码分析

    1:最全字节码指令分析

    2:面试题


    一: 操作数栈字节码指令

    1:编写源码

    1. public class OperandStackTest {
    2. public void testAndOperation(){
    3. byte i = 15;
    4. int j = 8;
    5. int k = i+j;
    6. }
    7. }

    2:javap解释整理字节码

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

    1. PS D:\code\study\hadoop\shit\target\classes> javap -verbose .\OperandStackTest.class
    2. Classfile /D:/code/study/hadoop/shit/target/classes/OperandStackTest.class
    3. Last modified 2023119日; size 421 bytes
    4. SHA-256 checksum 487149a1edc4d19af0b1fe2369086c27c8765ff5e011491d1028d4f6cf1d9746
    5. Compiled from "OperandStackTest.java"
    6. public class OperandStackTest
    7. minor version: 0
    8. major version: 52
    9. flags: (0x0021) ACC_PUBLIC, ACC_SUPER
    10. this_class: #2 // OperandStackTest
    11. super_class: #3 // java/lang/Object
    12. interfaces: 0, fields: 0, methods: 2, attributes: 1
    13. Constant pool:
    14. #1 = Methodref #3.#19 // java/lang/Object."":()V
    15. #2 = Class #20 // OperandStackTest
    16. #3 = Class #21 // java/lang/Object
    17. #4 = Utf8
    18. #5 = Utf8 ()V
    19. #6 = Utf8 Code
    20. #7 = Utf8 LineNumberTable
    21. #8 = Utf8 LocalVariableTable
    22. #9 = Utf8 this
    23. #10 = Utf8 LOperandStackTest;
    24. #11 = Utf8 testAndOperation
    25. #12 = Utf8 i
    26. #13 = Utf8 B
    27. #14 = Utf8 j
    28. #15 = Utf8 I
    29. #16 = Utf8 k
    30. #17 = Utf8 SourceFile
    31. #18 = Utf8 OperandStackTest.java
    32. #19 = NameAndType #4:#5 // "":()V
    33. #20 = Utf8 OperandStackTest
    34. #21 = Utf8 java/lang/Object
    35. {
    36. public OperandStackTest();
    37. descriptor: ()V
    38. flags: (0x0001) ACC_PUBLIC
    39. Code:
    40. stack=1, locals=1, args_size=1
    41. 0: aload_0
    42. 1: invokespecial #1 // Method java/lang/Object."":()V
    43. 4: return
    44. LineNumberTable:
    45. line 1: 0
    46. LocalVariableTable:
    47. Start Length Slot Name Signature
    48. 0 5 0 this LOperandStackTest;
    49. public void testAndOperation();
    50. descriptor: ()V
    51. flags: (0x0001) ACC_PUBLIC
    52. Code:
    53. stack=2, locals=4, args_size=1
    54. 0: bipush 15
    55. 2: istore_1
    56. 3: bipush 8
    57. 5: istore_2
    58. 6: iload_1
    59. 7: iload_2
    60. 8: iadd
    61. 9: istore_3
    62. 10: return
    63. LineNumberTable:
    64. line 3: 0
    65. line 4: 3
    66. line 5: 6
    67. line 6: 10
    68. LocalVariableTable:
    69. Start Length Slot Name Signature
    70. 0 11 0 this LOperandStackTest;
    71. 3 8 1 i B
    72. 6 5 2 j I
    73. 10 1 3 k I
    74. }
    75. SourceFile: "OperandStackTest.java"

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

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

            show ByteCode with JclassLib:

            最终显示结果如下:

    二:字节码分析

    1:最全字节码指令分析

    1. public class OperandStackTest {
    2. public void testAndOperation(){
    3. byte i = 15;
    4. int j = 8;
    5. int k = i+j;
    6. }
    7. }
    1. 0 bipush 15
    2. 2 istore_1
    3. 3 bipush 8
    4. 5 istore_2
    5. 6 iload_1
    6. 7 iload_2
    7. 8 iadd
    8. 9 istore_3
    9. 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这样的一个操作,将上一个方法的返回值加载到此栈帧的操作数栈中。

    1. public int getSum(){
    2. int m = 10;
    3. int n = 20;
    4. int k = m+n;
    5. return k;
    6. }
    7. public void testGetSum(){
    8. //aload_0获取上一个栈帧返回的结果,并保存在操作数栈中。
    9. int i = getSum();
    10. int j = 10;
    11. }

    2:面试题

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

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

  • 相关阅读:
    Mysql互不关联的联表查询(减少了查询的次数)
    8个常见的机器学习算法的计算复杂度总结
    【Rust日报】2022-08-20 将 Rust 带入太空 - 为 VA108XX MCU 系列建立 Rust 生态系统
    深入理解跳表及其在Redis中的应用
    36个精美完整网站网页完整源码HTML+CSS+JS
    基于51单片机的八路多路温度测控系统proteus仿真原理图PCB
    ubuntu镜像里装东西
    微服务外交官-Feign
    【Django】REST_Framework框架——Mixin类和GenericAPIView中的视图子类源码解析
    复杂环境下多移动机器人路径规划研究附Matlab代码
  • 原文地址:https://blog.csdn.net/Facial_Mask/article/details/134319490