局部变量表
局部变量表存放方法的参数,以及方法内定义的局部变量。用以存储的内存以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位置是变量another。bytes 的分配没有了。
这里就是栈帧内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会被回收分配给 another,bytes1 的slot没有回收,因此他指向堆内1M的空间没有被GC回收。