Java栈——局部变量表

局部变量表

局部变量表存放方法的参数,以及方法内定义的局部变量。用以存储的内存以slot为单位,每个slot是一个32位(4bytes)内存空间。

以下的字节码文件的方法定义部分 add() 方法局部变量的定义。

例如:

public class Test1 {
    public int add(int a, int b) {
        int c = a + b;
        return c;
    }
}

输出的字节码文件:

//...省略部分
{
  public com.sanren1024.jvm.Test1();
    descriptor: ()V
    flags: (0x0001) 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/sanren1024/jvm/Test1;

  public int add(int, int);
    descriptor: (II)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: istore_3
         4: iload_3
         5: ireturn
      LineNumberTable:
        line 6: 0
        line 7: 4
      LocalVariableTable:        // 局部变量表
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/sanren1024/jvm/Test1;  // slot0位置this
            0       6     1     a   I
            0       6     2     b   I
            4       2     3     c   I        // 方法内局部变量c
}
SourceFile: "Test1.java"

从上述字节码文件的add()定义部分可以看到。局部变量表(LocalVariableTable)中有4个item。其中一列是Slot,slot0位置是this,这也说明了 add() 方法是实例方法,其内部可以访问到方法外的实例变量等。

对应这源文件定义,add(int a, int b) 方法定义有两个参数a,b,分别对应了局部变量表中的slot1,slot2位置,slot3位置是c,也就是方法内定义的 int c = a + b; 语句中的c。

若是static方法,slot0位置就不是 this.

public class Test1 {
    public static int  add(int a, int b) {
        int c = a + b;
        return c;
    }
}

方法定义的字节码部分。

//....省略部分
{
  public com.sanren1024.jvm.Test1();
    descriptor: ()V
    flags: (0x0001) 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/sanren1024/jvm/Test1;

  public static int add(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: istore_2
         4: iload_2
         5: ireturn
      LineNumberTable:
        line 6: 0
        line 7: 4
      LocalVariableTable:      // 局部变量表
        Start  Length  Slot  Name   Signature
            0       6     0     a   I          // slot0位置不再是this,而是a
            0       6     1     b   I
            4       2     2     c   I
}
SourceFile: "Test1.java"

同样看到 add() 方法的定义,在局部变量表部分slot0位置不是this.

Slot作用域

slot是复用的,复用为了节省栈帧空间的大小。但同样存在一个作用域的问题。

public class Test1 {

    public static void main(String[] args) {
        byte[] bytes = new byte[2 * 1024 * 1024]; // 申请2M的内存空间

        System.gc();

        System.out.println("totalMemory=" + (Runtime.getRuntime().totalMemory() /1024.0 /1024.0) + "M");
        System.out.println("freeMemory=" + (Runtime.getRuntime().freeMemory() /1024.0 /1024.0) +  "M");
        System.out.println("maxMemory=" + (Runtime.getRuntime().maxMemory() /1024.0 /1024.0) + "M");
    }
}
// 运行JVM参数:-XX:+UseConcMarkSweepGC -Xms8m -Xmx8m -Xlog:gc+heap=debug

上述代码运行后。

totalMemory=7.75M
freeMemory=4.7150726318359375M
maxMemory=7.75M

可以看到打印结果,可用内存是4.7M左右,main() 方法中局部变量bytes分配2M空间。

对应字节码slot分配:

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      87     0  args   [Ljava/lang/String;    // main()参数
            5      82     1 bytes   [B   // 局部变量

这里,bytes 的作用域在 main() 方法内,bytes 指向对内2M的空间没有改变。 GC操作不会回收内存。

修改下源码,将bytes的作用域修改

public static void main(String[] args) {
    {
        byte[] bytes = new byte[2 * 1024 * 1024];
    }
    System.gc();

    System.out.println("totalMemory=" + (Runtime.getRuntime().totalMemory() /1024.0 /1024.0) + "M");
    System.out.println("freeMemory=" + (Runtime.getRuntime().freeMemory() /1024.0 /1024.0) +  "M");
    System.out.println("maxMemory=" + (Runtime.getRuntime().maxMemory() /1024.0 /1024.0) + "M");
}

运行后

totalMemory=7.75M
freeMemory=4.715202331542969M
maxMemory=7.75M

这一看,与修改前运行结果一致。OK,再做一个修改。

public static void main(String[] args) {
    {
        byte[] bytes = new byte[2 * 1024 * 1024];
    }

    int another = 3;

    System.gc();

    System.out.println("totalMemory=" + (Runtime.getRuntime().totalMemory() /1024.0 /1024.0) + "M");
    System.out.println("freeMemory=" + (Runtime.getRuntime().freeMemory() /1024.0 /1024.0) +  "M");
    System.out.println("maxMemory=" + (Runtime.getRuntime().maxMemory() /1024.0 /1024.0) + "M");
}

在bytes外,声明另一个变量,运行的结果.

totalMemory=7.75M
freeMemory=6.715065002441406M
maxMemory=7.75M

比第一次运行后,可用空间大了2M左右。主要区别只在于多声明了一个局部变量。

看看对应字节码的slot部分。

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      89     0  args   [Ljava/lang/String;
            7      82     1 another   I

可以看到,其中slot0是参数args,slot1位置是变量anotherbytes 的分配没有了。

这里就是栈帧内slot的复用的结果。 {}内bytes的作用域结束后,理论上对应的slot就不使用了,再声明another变量时,会将原来bytes分配的slot分配给another。因此原来堆内分配的2M空间会被GC回收。

可以通过比较两次的字节码局部变量slot位置的变量名看到区别。

再做一个修改

public static void main(String[] args) {
    {
        byte[] bytes = new byte[1 * 1024 * 1024];
        byte[] bytes1 = new byte[1 * 1024 * 1024];
    }

    System.gc();

    System.out.println("totalMemory=" + (Runtime.getRuntime().totalMemory() /1024.0 /1024.0) + "M");
    System.out.println("freeMemory=" + (Runtime.getRuntime().freeMemory() /1024.0 /1024.0) +  "M");
    System.out.println("maxMemory=" + (Runtime.getRuntime().maxMemory() /1024.0 /1024.0) + "M");
}

分2次分别要求1M空间,运行之后结果。

totalMemory=7.75M
freeMemory=4.741065979003906M
maxMemory=7.75M

这样,{}内分配到的空间大小2M左右,没有被GC回收。

其对应字节码slot部分。

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5       5     1 bytes   [B
            0      92     0  args   [Ljava/lang/String;

在{}外重新声明一个变量。

public static void main(String[] args) {
    {
        byte[] bytes = new byte[1 * 1024 * 1024];
        byte[] bytes1 = new byte[1 * 1024 * 1024];
    }
    int another = 3;

    System.gc();

    System.out.println("totalMemory=" + (Runtime.getRuntime().totalMemory() /1024.0 /1024.0) + "M");
    System.out.println("freeMemory=" + (Runtime.getRuntime().freeMemory() /1024.0 /1024.0) +  "M");
    System.out.println("maxMemory=" + (Runtime.getRuntime().maxMemory() /1024.0 /1024.0) + "M");
}

运行后结果。

totalMemory=7.75M
freeMemory=5.741058349609375M
maxMemory=7.75M

对应的字节码slot部分。

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5       5     1 bytes   [B
            0      94     0  args   [Ljava/lang/String;
           12      82     1 another   I

结果显示slot1位置是bytes,后变成了another

这里运行结果可用内存与 another 声明前相差1M左右。原因是 bytes, bytes1 占了两个slot,在{}作用域外,声明 another 后,bytes 变量的slot会被回收分配给 anotherbytes1 的slot没有回收,因此他指向堆内1M的空间没有被GC回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VoidHope

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值