JVM 系列 - 内存区域 - Java 虚拟机栈(三)

2018-12-04  本文已影响0人  _晓__

特点

Java 虚拟机栈会出现的异常

Java 虚拟机栈执行过程

可以参考一下这篇文章:https://blog.csdn.net/azhegps/article/details/54092466


栈帧(Stack Frame)

局部变量表(Local Variable Table)

使用一段代码说明一下局部变量表:

// java 代码
public int test() {
    int x = 0;
    int y = 1;
    return x + y;
}

// javac 编译后的字节码,使用 javap -v 查看
public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: ireturn
      LineNumberTable:
        line 7: 0
        line 8: 2
        line 9: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  this   Lcom/alibaba/uc/TestClass;
            2       6     1     x   I
            4       4     2     y   I

对应上面的解释说明,通过 LocalVariableTable 也可以看出来:
Code 属性:
stack(int x(1个栈深度)+ int y(1个栈深度))=2, locals(this(1 Slot)+ int x(1 Slot)+ int y(1 Slot))=3, args_size(非 static 方法,this 隐含参数)=1

验证 Slot 复用,运行以下代码时,在 VM 参数中添加 -verbose:gc

public void test() {
   {
      byte[] placeholder = new byte[64 * 1024 * 1024];
   }
   int a = 0; // 当这段代码注释掉时,System.gc() 执行后,也并不会回收这64MB内存。当这段代码执行时,内存被回收了
   System.gc();
}

局部变量表中的 Slot 是否还存在关于 placeholder 数组对象的引用。当 int a = 0; 不执行时,代码虽然已经离开了 placeholder 的作用域,但是后续并没有任何对局部变量表的读写操作,placeholder 原本所占用的 Slot 还没有被其他变量所复用,所以 placeholder 作为 GC Roots(所有 Java 线程当前活跃的栈帧里指向 Java 堆里的对象的引用) 仍然是可达对象。当 int a = 0; 执行时,placeholder 的 Slot 被变量 a 复用,所以 GC 触发时,placeholder 变成了不可达对象,即可被 GC 回收。

操作数栈(Operand Stack)

// java 代码
public void test() {
     byte a = 1;
     short b = 1;
     int c = 1;
     long d = 1L;
     float e = 1F;
     double f = 1D;
     char g = 'a';
     boolean h = true;
}

// 字节码指令
0: iconst_1   // 把 a 压入操作数栈栈顶
1: istore_1   // 将栈顶的 a 存入局部变量表索引为1的 Slot
2: iconst_1  // 把 b 压入操作数栈栈顶
3: istore_2   // 将栈顶的 b 存入局部变量表索引为2的 Slot
4: iconst_1   // 把 c 压入操作数栈栈顶
5: istore_3    // 将栈顶的 c 存入局部变量表索引为3的 Slot
6: lconst_1   // 把 d 压入操作数栈栈顶
7: lstore        4   // 将栈顶的 d 存入局部变量表索引为4的 Slot,由于 long 是64位,所以占2个 Slot
9: fconst_1   // 把 e 压入操作数栈栈顶
10: fstore        6   // 将栈顶的 e 存入局部变量表索引为6的 Slot
12: dconst_1   // 把 f 压入操作数栈栈顶
13: dstore        7   // 将栈顶的 f 存入局部变量表索引为4的 Slot,由于 double 是64位,所以占2个 Slot
15: bipush        97   // 把 g 压入操作数栈栈顶
17: istore        9   // 将栈顶的 g 存入局部变量表索引为9的 Slot
19: iconst_1   // 把 h 压入操作数栈栈顶
20: istore        10   // 将栈顶的 h 存入局部变量表索引为10的 Slot

从上面字节码指令可以看出来,除了 long、double、float 类型使用的字节码指令不是 iconstistore,其他类型都是使用这两个字节码指令操作,说明 byte、short、char、boolean 进入操作数栈时,都会被转化成 int 型。

动态连接(Dynamic Linking)

// java 代码
 public Test test() {
    return new Test();
 }

// 字节码指令
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#20         // com/alibaba/uc/Test.i:I
   #3 = Class              #21            // com/alibaba/uc/Test
   #4 = Class              #22            // java/lang/Object
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/alibaba/uc/Test;
  #14 = Utf8               test
  #15 = Utf8               ()I
  #16 = Utf8               <clinit>
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #7:#8          // "<init>":()V
  #20 = NameAndType        #5:#6          // i:I
  #21 = Utf8               com/alibaba/uc/Test
  #22 = Utf8               java/lang/Object

public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: getstatic     #2                  // Field i:I
         3: areturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lcom/alibaba/uc/Test;

从上面字节码指令看出 0: getstatic #2 // Field i:I 这行字节码指令指向 Constant pool 中的 #2,而 #2 中指向了 #3 和 #20 为符号引用,在类加载过程的解析阶段会被转化为直接引用(指向方法区的指针)。

方法返回地址

简述:

虚拟机会使用针对每种返回类型的操作来返回,返回值将从操作数栈出栈并且入栈到调用方法的方法栈帧中,当前栈帧出栈,被调用方法的栈帧变成当前栈帧,程序计数器将重置为调用这个方法的指令的下一条指令。

附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。

上一篇 下一篇

猜你喜欢

热点阅读