概念
- 局部变量表也被称之为局部变量数组或本地变量表
- 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress返回值类型。
- 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
- 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
- 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,进而方法调用就会占用更多的栈空间,导致其嵌套调用次数就会减少
- 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁
局部变量表举例
```java package com.lyd;
import java.util.Date;
public class LocalVariablesTest { private int count = 0;
public static void main(String[] args) {
LocalVariablesTest test = new LocalVariablesTest();
int num = 10;
test.test1();
}
//练习:
public static void testStatic(){
LocalVariablesTest test = new LocalVariablesTest();
Date date = new Date();
int count = 10;
System.out.println(count);
//因为this变量不存在于当前方法的局部变量表中!!
// System.out.println(this.count); }
//关于Slot的使用的理解
public LocalVariablesTest(){
this.count = 1;
}
public void test1() {
Date date = new Date();
String name1 = "atguigu.com";
test2(date, name1);
System.out.println(date + name1);
}
public String test2(Date dateP, String name2) {
dateP = null;
name2 = "songhongkang";
double weight = 130.5;//占据两个slot
char gender = '男';
return dateP + name2;
}
public void test3() {
this.count++;
}
public void test4() {
int a = 0;
{
int b = 0;
b = a + 1;
}
//变量c使用之前已经销毁的变量b占据的slot的位置
int c = a + 1;
}
}
```
看字节码信息,可得结论:所以局部变量表所需的容量大小是在编译期确定下来的。
jclasslib看字节码方法介绍
通过jclasslib打开当字节码文件。
点开具体的方法,能看到Code就是方法里面的代码,点开,里面的标签栏字节码就是具体执行代码的指令,红色的是行号,异常表就是记录异常的
杂项里面
在点开Code能看到,LineNumberTable行号表。
- 起始PC:表示字节码指令的行号
- 行号:表示java代码的行号
这两个一一对应
LocalVariableTable局部变量表
- 起始PC:这个变量作用域的开始位置
- 长度:这个变量作用域的范围,从起始位置开始,这个长度就是当前变量的作用域范围。Length== 5表示局部变量剩余有效行数,main方法字节码指令总共有16行,从11行开始生效,那么剩下就是16-11 ==5。
结合行号表和java代码看,test参数起始PC是8,表示java的行号是10,java代码里面test在第9行声明,test起作用就是在10行开始,到整个方法结束。起始PC+长度=字节码指令长度
- 序号:变量声明的顺序索引
- 名字:参数或局部变量名
- 描述符:是什么类型的参数
关于Slot
- 参数值的存放总是从局部变量数组索引 0 的位置开始,到数组长度-1的索引结束。
- 局部变量表,最基本的存储单元是Slot(变量槽),局部变量表中存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress类型的变量。
- 在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型占用两个slot(1ong和double)。
- byte、short、char在储存前被转换为int,boolean也被转换为int,0表示false,非0表示true,float也占用一个slot。
- long和double则占据两个slot
- JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值
- 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上
- 如果需要访问局部变量表中一个64bit的局部变量值时,只需要访问它的开始索引即可。(比如:访问long或double类型变量,long占用3和4的索引,想要获取这个long,只需要访问索引3就行了)
- 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。(this也相当于一个变量)
Slot代码例子
例子1:this 存放在 index = 0 的位置
例子2:64位的类型(1ong和double)占用两个slot
例子3:static 无法调用 this
因为静态方法的局部变量表里面不存在this,所以无法咋静态方法里面用this
例子4:Slot的重复利用
可以看到最大变量数量只有3个,this,a,b,c有四个了,少了谁?
可以看到b的作用域只有那个大括号范围,c重用了b的插槽。他们的作用域没有重合,这样做节约内存。
成员变量与局部变量的对比
变量的分类:
- 按照数据类型分类:①基本数据类型(8种),引用数据类型
- 按照在类中声明的位置分类:
- 成员变量:在使用前,都经历过默认初始化赋值
- 类变量(静态变量):linking的prepare阶段:给类变量默认赋值—-> initial阶段:给类变量显式赋值即静态代码块赋值
- 实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值
- 局部变量:在使用前,必须要进行显式赋值的!否则,编译不通过。
- 成员变量:在使用前,都经历过默认初始化赋值
- 参数表分配完毕之后,再根据方法体内定义的变量的顺序和作用域分配。
- 我们知道成员变量有两次初始化的机会,第一次是在“准备阶段”,执行系统初始化,对类变量设置零值,另一次则是在“初始化”阶段,赋予程序员在代码中定义的初始值。类加载子系统-准备阶段
- 和类变量(静态变量)初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。
关于局部变量表调优说明
- 在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。
- 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。