栈帧的内部结构--局部变量表(Local Variables) 附:JVM学习目录

每个栈帧中包含:

  • 局部变量表(Local Variables)
  • 操作数栈(Opreand Stack) 或表达式栈
  • 动态链接 (Dynamic Linking) (或指向运行时常量的方法引用)
  • 动态返回地址(Return Address) (或方法正常退出或者异常退出的引用的定义)
  • 一些附加信息

局部变量表

  • 局部变量表也被称之为局部变量数据组或本地变量表
  • 定义为一个数字数组,主要用户存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress 类型
  • 由于局部变量表示建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
  • 局部变量表所需要的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximun local variables数据项中,在方法运行期间时不会改变局部变量表的大小的
  • 方法嵌套调用的次数由栈的大小决定,一般来说,栈越大,方法嵌套调用次数越多,对于一个函数而言,他的参数和局部变量越多,使得局部变量表膨胀,它的栈就越大,以满足方法调用所需传递的信息增大的需求,进而函数调用就会占更多的栈空间,导致其嵌套调用次数就会减少
  • 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当方法调用结束后,随着方法栈的销毁,局部变量表也会随之销毁

局部变量表的存储单元Slot(变量槽)

  • 参数值的存放总是在局部变量数据的index0开始,到数组长度-1的索引结束
  • 局部变量表,最基本的存储单元是Slot(变量槽)
  • 局部变量表中存放编译期可知的各种基本数据类型(8种)、引用类型(reference)、returnAdderss类型的变量
  • 在局部变量表中,32位以内的类型只占一个slot(包括returnAdderss类型)64位的类型(long和double)占用两个slot
    • byte、short、char在存储前被转换为int,boolean也被准换为int,0为false,1为true
    • long和double则占据两个slot
  • JVM会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引即可成功的访问到局部变量表中指定的局部变量值
  • 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量表将会按照顺序被复制到局部变量表中的每一个slot上
  • 如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可
  • 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会被存放在index为0的slot处,其余的参数按照参数表顺序继续排列

栈帧的内部结构--局部变量表(Local Variables)
附:JVM学习目录

 各个类型占用slot图示

 

  • slot也是可以进行重复利用的,即如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期的局部变量的槽位,从而达到节约资源的目的。主要的运用方式如下所示,其中{}外作用域就结束了
    //出了大括号就不能用了
    public void LocalVar(){
            {
                int a= 0;
                System.out.println(a);
            }
            //此时b就会复用a的槽位
            int b=0;
    }

常见问题

Q:为什么static中不能调用this方法

A:因为this的索引只存在构造方法和实例方法的局部变量表中,而static是在初始化方法<clinit>中进行初始化的,里面没有保存this变量的索引

Q:变量的分类

A:

    • 按照数据类型分类
      • 基本数据类型
      • 引用数据类型(对象)
    • 按照在类中的位置分类
      • 成员变量:类中变量,在使用前,都经历默认初始化赋值
        • 类变量,Linking的prepare阶段,给类变量默认赋值–>initial阶段,给类变量显示赋值,即静态代码块赋值
        • 实例变量,随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值
      • 局部变量:方法中的变量。在使用时必须进行显示的赋值,否则编译不通过

Q:局部变量与成员变量的对比

A:

    • 参数表分配完毕以后,在根据方法体内定义的变量的顺序和作用域分配
    • 我们知道类变量表有两次初始化的机会,第一次是在“准备阶段”,执行系统初始化,对类变量设置零值,另一次是在“初始化”阶段,赋予变量在代码中定义的初始值
    • 和类变量初始化不同的是,局部变量表不存在系统初始化过程中,这就意味着一旦定了局部变量则必须认为的初始化,否则无法使用
public void test(){
    int i;
    Systme.out.println(i);
    //这种是编译不通过的
}

其他

  • 在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表,在方法执行时,虚拟机使用局部变量表完成方法的传递
  • 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中的直接或者间接引用的对象都不会被回收
  • 也可以新建局部对象