重学JVM之虚拟机栈

虚拟机栈是程序方法执行的地方。


JVM虚拟机栈.png

虚拟机栈中每一个栈帧可以理解为一个方法。每个栈帧中包涵执行方法所需要的操作数栈、局部变量表、动态链接和返回地址。

局部变量表

用来存放方法运行时的传入的参数和方法内部的局部变量,主要是存放Java的8大基础类型,如果是对象的话,则存放对象的引用地址,实际对象会在堆上分配内存(大部分情况下,有少数情况也会有栈上分配的情况)。局部变量的大小最大容量会编译后记录在class文件中。

操作数栈

用来存放和当前操作相关的数据。

动态链接

当方法内调用其他方法时,因为Java语言的多态特性,需要依靠动态链接找到实际需要执行的方法。即将符号引用转换为直接引用。

返回地址

正常返回:
调用程序计数器中的地址作为返回
异常返回:
返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息

怎么看字节码指令呢?

下面是笔者常用的几种方法

  • javap命令,操作比较麻烦一点,但还是很好用的
  • 插件:ASM Bytecode Viewer,安装完成后在文件中左键,选择功能即可
  • Kotlin代码,Tools > Kotlin > Show Kotlin Bytecode

方法2、3都是在IDEA/AS开发工具中用到的。最近Kotlin用的比较多,所以常用第三种。


举个例子.jpg

源码:

fun add():Int {
    val value1 = 1
    val value2 = 7

    val value3 = value1+value2

    return value3 + 3
}

Kotlin Bytecode处理后:

// ================com/chenlong/KtTestKt.class =================
// class version 50.0 (50)
// access flags 0x31
public final class com/chenlong/KtTestKt {
  // access flags 0x19
  public final static add()I
   L0
    LINENUMBER 4 L0
    ICONST_1
    ISTORE 0
   L1
    LINENUMBER 5 L1
    BIPUSH 7
    ISTORE 1
   L2
    LINENUMBER 7 L2
    ILOAD 0
    ILOAD 1
    IADD
    ISTORE 2
   L3
    LINENUMBER 9 L3
    ILOAD 2
    ICONST_3
    IADD
    IRETURN
   L4
    LOCALVARIABLE value3 I L3 L4 2
    LOCALVARIABLE value2 I L2 L4 1
    LOCALVARIABLE value1 I L1 L4 0
    MAXSTACK = 2
    MAXLOCALS = 3

  @Lkotlin/Metadata;(mv={1, 4, 0}, bv={1, 0, 3}, k=2, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0008\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u00a8\u0006\u0002"}, d2={"add", "", "d1023"})
  // compiled from: KtTest.kt
}

现在开始读读读读读
LX这个可以不用管
LINENUMBER X X也可以不用(LINENUMBER 4,这个的4其实.java文件的行号,不看也可以)
下面逐行阅读:

  1. 初始情况


    JVM虚拟机栈操作0.png
  1. ICONST_1:把常量 1 压入操作数栈栈顶

    JVM虚拟机栈操作1.png

  2. ISTORE 0:把操作数栈顶的元素出栈并放入局部变量表下标 0的位置

    JVM虚拟机栈操作2.png

  3. BIPUSH 7:把常量7压入操作数栈栈顶
    为什么7是BIPUSH而1是ICONST呢?
    这应该是JVM的优化吧,具体我也不懂。
    -128到127之间,-1到5是ICONST,其他是BIPUSH,-128以下和127以上用SIPUSH
    我是怎么知道的?
    Kotlin Bytecode很强大,实时翻译,稍微测一下就知道了!

    JVM虚拟机栈操作3.png

  4. ISTORE 1:把操作数栈顶的元素出栈并放入局部变量表下标 1的位置

    JVM虚拟机栈操作4.png

  5. ILOAD 0:把局部变量下标 0的值放入操作数栈栈顶

    JVM虚拟机栈操作5.png

  6. ILOAD 1:把局部变量下标 1的值放入操作数栈栈顶

    JVM虚拟机栈操作7.png

  7. IADD:将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶

    JVM虚拟机栈操作8.png

  8. ISTORE 2:把操作数栈顶的元素出栈并放入局部变量表下标 2的位置

    JVM虚拟机栈操作9.png

  9. ILOAD 2:把局部变量下标 2的值放入操作数栈栈顶

    JVM虚拟机栈操作10.png

  10. ICONST_3:把常量 3压入操作数栈栈顶

    JVM虚拟机栈操作11.png

  11. IADD:将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶

    JVM虚拟机栈操作12.png

  12. IRETURN:把操作数栈里面的11返回。然后整个栈帧从虚拟机栈中出栈,局部变量表和操作数栈完成任务,拉出去销毁。

从这里可以看到基于栈实现的JVM,运行起来还是蛮复杂的。

你可能感兴趣的:(重学JVM之虚拟机栈)