美文网首页
JVM(二)虚拟机栈

JVM(二)虚拟机栈

作者: NIIIICO | 来源:发表于2022-03-28 18:55 被阅读0次

JVM(一)简介对运行时数据区有了大概的了解,下面详细介绍虚拟机栈(Java Virtual Machine stack)。

虚拟机栈结构

每个JVM线程都有一个JVM栈,栈中保存着栈帧(Frame)。

一、工具

编译如下代码:

package com.niiiico.wgdemo;

public class Test {
    public int testAdd() {
        int a = 10;
        int b = 20;
        int c = a + b;
        return c;
    }
}

1、javap命令

将java文件编译成class后,通过javap -v Test.class即可查看类相关的信息。

$ javap -v Test.class
Classfile /D:/workplace/WGDemo/app/build/intermediates/javac/debug/classes/com/niiiico/wgdemo/Test.class
  Last modified 2022-3-28; size 417 bytes
  MD5 checksum bf610cf1af0aaa5f64ff7104523a160d
  Compiled from "Test.java"
public class com.niiiico.wgdemo.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

// 常量池
Constant pool:
   #1 = Methodref          #3.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // com/niiiico/wgdemo/Test
   #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               Lcom/niiiico/wgdemo/Test;
  #11 = Utf8               testAdd
  #12 = Utf8               ()I
  #13 = Utf8               a
  #14 = Utf8               I
  #15 = Utf8               b
  #16 = Utf8               c
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #4:#5          // "<init>":()V
  #20 = Utf8               com/niiiico/wgdemo/Test
  #21 = Utf8               java/lang/Object
{

  // 构造方法
  public com.niiiico.wgdemo.Test();
    descriptor: ()V
    flags: 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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/niiiico/wgdemo/Test;

  // testAdd方法
  public int testAdd();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      // 操作数栈深2,本地变量表长度4(this、a、b、c)
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: iload_3
        11: ireturn

      // 源码中的行号与字节码行号对应关系,如:源码中 int a = 10;字节码中在第0行。
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  this   Lcom/niiiico/wgdemo/Test;
            3       9     1     a   I
            6       6     2     b   I
           10       2     3     c   I
}
SourceFile: "Test.java"

2、jclasslib Bytecode Viewer

(1)点击file -> setting -> Plugin -> Marketplace,搜索jclasslib插件,进行安装。


jclasslib插件

(2)书写java代码,点击Build -> Rebuild Project,重新编译。


Build工程

(3)通过app -> build -> intermediates -> javac -> 包名,找到class文件。


查看class文件

(4)点击view -> Show Bytecode With Jclasslib查看class结构


查看class结构

(5)查看常量池,方法等结构。


class结构

二、栈帧

栈帧用来存储数据和部分结果、执行动态链接、方法返回和分发异常。栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址。

对于活动线程,位于栈顶的帧是当前帧,对应的方法是当前方法。当前方法调用新的方法时,会创建新的栈帧,此时新的栈帧变为当前帧;方法返回时,如果有返回值,会将结果返回上一帧,同时该帧被销毁,上一帧变为当前帧。

1、局部变量表

每个帧拥都有一个数组来保存自己的局部变量,数组长度在编译时决定,具体大小可以在编译后的class文件中看到。

局部变量表以Slot(变量槽,32位)为单位,一个Slot可以保存boolean、byte、char、short、int、float、reference、returnAddress类型的数据,两个Slot可以保存long、double类型的数据。

如果执行的方法是实例方法,第0位用来传递对象本身的引用(this),其余位置用来表示其他参数。

局部变量表示例 jclasslib看到的局部变量表

通过jclasslib可以看到详细的局部变量表,如:this、a、b、c等。其中起始PC表示从第几行开始生效,长度表示生效行数。

2、操作数栈

每个帧都有一个后进先出的操作数栈,操作数栈的最大深度也是在编译时确定的。

帧创建时,操作数栈是空的,JVM通过指令向里存入或取出常量、变量的值。它也可以用来准备传递给方法的参数、接收方法的返回结果。

操作数栈示例

3、动态链接

动态链接指向当前方法所在类的运行时常量池。

我们知道方法区会存储每个类的信息,而类信息中有运行时常量池用来标记方法。当前方法如果需要调用其他方法的时候,可以通过动态链接从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,就能直接调用对应方法。

4、方法返回地址

存放调用方法的寄存器值。

方法结束有两种方式:正常执行完成、抛出异常
正常返回时:将寄存器的值作为返回地址;如果方法有返回值,也会向调用方法返回一个值。
发生异常时:会去异常表中查询指令,如果没有查到,。
无论哪种方式退出,都会返回到该方法被调用的位置。

package com.niiiico.wgdemo;

import android.util.Log;

public class Test {
    public Test() {
    }

    public void testAdd() {
        try {
            boolean var1 = true;
        } catch (Exception var2) {
            Log.v("AAAAA", var2.toString());
        }

    }
}


字节码:
 0 iconst_3
 1 istore_1
 2 goto 16 (+14)
 5 astore_1
 6 ldc #3 <AAAAA>
 8 aload_1
 9 invokevirtual #4 <java/lang/Exception.toString : ()Ljava/lang/String;>
12 invokestatic #5 <android/util/Log.v : (Ljava/lang/String;Ljava/lang/String;)I>
15 pop
16 return

上述代码,查看class结构,可以看到异常表如下图所示:


异常表

表示字节码0-2(源码8-11)行可能会有异常,如果发生异常,跳转到字节码第5行(源码第9行)执行。

源码中的行号与字节码行号

三、方法执行流程

下图展示了方法执行过程中,局部变量表、操作数栈、程序计数器的数据变化。


方法执行流程

四、其他

1、空构造函数栈深

空构造函数栈深

观察发现空构造函数的栈深是1,看如下字节码,发现调用了aload_0,表示将this装载到了操作数栈中,然后通过return将this返回。

0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 return

2、局部变量表slot复用

package com.niiiico.wgdemo;

import android.util.Log;

public class Test {
    public void testAdd() {
        int a = 1;
        {
            int b = a + 1;
            Log.v("AAAAA", String.valueOf(b));
        }
        int c = a + 1;
    }
}

如上述代码,this、a、b、c,四个变量,但是由于变量b的有效周期为6-15行,变量b、c类型相同,作用范围不重叠,从而产生复用。

局部变量表slot复用

相关文章

网友评论

      本文标题:JVM(二)虚拟机栈

      本文链接:https://www.haomeiwen.com/subject/yfmkjrtx.html