Skip to content

局部变量表(slot/静态vs局部变量)

局部变量表是什么

局部变量表(Local Variable Table)是栈帧中用于存储方法参数和局部变量的区域。它是 JVM 字节码执行的重要支撑,理解它有助于理解方法的参数传递、变量作用域,以及为什么局部变量不存在线程安全问题。

Slot:局部变量表的基本单位

局部变量表是一个以 Slot 为单位的数组。Slot 是 JVM 分配给变量的最小存储单位,大小为 4 字节(32 位)。

类型与 Slot 的关系

类型占用 Slot 数说明
intshortbytecharbooleanfloat1 Slot单个 Slot
longdouble2 Slot需要连续的 2 个 Slot
reference(对象引用)1 Slot4 字节,存储对象地址
java
public class SlotUsage {
    public void method(
        int i,      // slot 1
        long l,     // slot 2-3(占2个Slot)
        double d,   // slot 4-5(占2个Slot)
        Object o    // slot 6(reference)
    ) {
        int a = 1;       // slot 7
        long b = 2L;     // slot 8-9(占2个Slot)
        String s = "";   // slot 10(reference)
    }
}

Slot 复用与 GC

Slot 复用会影响 GC 的可达性分析:

java
public class SlotReuseGC {
    Object obj = new Object();  // 类变量,存在堆中

    public void method() {
        // slot 1 分配给 objRef
        objRef = new Object();
        // ... 使用 objRef
    }
}

如果 objRef 出了作用域,但它的 Slot 没有被后续变量复用,Slot 中仍然保存着对对象的引用,GC 就会认为这个对象仍然可达(即使代码中已经无法访问它了)。这是 JVM 的保守策略:宁可多留对象,也不提前回收。

实际上,由于 Slot 复用,这种情况在实践中很少成为问题。但理解这一点有助于理解 GC 的根节点枚举过程。

实例方法中的 this

非 static 方法中,局部变量表的第一个 Slot(slot 0)总是存储 this 引用——当前对象的引用。

java
public class ThisSlot {
    public void instanceMethod() {
        // slot 0: this(隐式传入)
        // slot 1: a
        int a = 10;
    }

    public static void staticMethod() {
        // 没有 this
        // slot 0: a
        int a = 10;
    }
}

为什么 static 方法没有 this?因为 static 方法属于类,不属于某个对象,自然没有 this 引用。

局部变量表与类变量(static 变量)的对比

这是面试中常见的问题:

维度局部变量类变量(static)
存储位置虚拟机栈(栈帧的局部变量表)方法区/元空间
生命周期方法调用时创建,方法结束销毁类加载时创建,类卸载时销毁
初始化必须手动赋值,否则编译错误默认零值 + 初始化块赋值
内存区域线程私有线程共享
GC方法结束即回收类卸载或 FullGC 时回收
java
public class VariableCompare {
    // 类变量(static):存在于方法区
    static int staticVar = 100;
    static { System.out.println("static block"); }

    public void method() {
        // 局部变量:存在于虚拟机栈的栈帧中
        int localVar = 200;

        // 关键区别:
        // - localVar 存在线程自己的栈上,另一个线程看不到
        // - staticVar 存在方法区,所有线程共享
    }
}

参数传递:值传递还是引用传递

这是另一个经典面试题。Java 永远是值传递,但需要分清「基本类型」和「引用类型」的情况。

基本类型参数传递

java
public class PrimitivePassByValue {
    public static void main(String[] args) {
        int num = 10;
        change(num);
        System.out.println(num);  // 输出 10
    }

    static void change(int num) {
        num = 20;  // 修改的是栈帧中 num 的副本
    }
}

引用类型参数传递

java
public class ReferencePassByValue {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        change(sb);
        System.out.println(sb);  // 输出 HelloWorld
    }

    static void change(StringBuilder sb) {
        // sb 存储的是引用的副本(副本也是地址)
        // 通过这个副本访问的是同一个 StringBuilder 对象
        sb.append("World");

        // 重新指向新对象,不影响原调用者
        sb = new StringBuilder("New");
    }
}

关键理解:把引用类型的变量理解为「遥控器」会更清晰。传递引用参数,相当于把你的遥控器的「复印件」传给了方法。方法可以用这个复印件换频道(修改对象内容),但没法把复印件换成另一个遥控器(重新赋值会影响复印件本身,但不影响原遥控器)。

局部变量表的字节码视角

加载到操作数栈

局部变量表中的值,需要通过 iloadaload 等指令加载到操作数栈才能使用:

java
public int loadDemo() {
    int a = 1;
    int b = 2;
    return a + b;
}

字节码:

java
iconst_1       // 常量 1 入栈(不使用局部变量)
istore_1       // 出栈存入 slot 1(a)
iconst_2       // 常量 2 入栈
istore_2       // 出栈存入 slot 2(b)
iload_1        // slot 1 的值入栈
iload_2        // slot 2 的值入栈
iadd           // 相加
ireturn        // 返回

iload 系列指令

JVM 为局部变量表的前 4 个 int 值提供了专用指令(更短更高效):

指令含义等价于
iload_0slot 0 入栈iload 0
iload_1slot 1 入栈iload 1
iload_2slot 2 入栈iload 2
iload_3slot 3 入栈iload 3
lload_0/1/2/3long 类型-
fload_0/1/2/3float 类型-
dload_0/1/2/3double 类型-
aload_0/1/2/3reference 类型-

超过 4 个的情况,使用通用指令 iload n(n 为 slot 编号)。

局部变量表与调试

局部变量表使得调试器能够在断点处查看变量的值:

java
public class DebugDemo {
    public static void main(String[] args) {
        int a = 1;          // ← 断点在这里
        int b = 2;          //        IDE 能看到 a = 1
        int c = a + b;      //        依赖局部变量表信息
        System.out.println(c);
    }
}

当用 javac -g 编译时,局部变量表会包含变量名信息;不带 -g 编译时,局部变量表只有类型信息,没有变量名(调试时 IDE 只显示 slot 编号)。

本节小结

局部变量表的核心要点:

关键点说明
Slot基本单位,4 字节;long/double 占 2 个
this非 static 方法的 slot 0
必须初始化局部变量必须赋值才能使用,编译器会检查
线程私有存在虚拟机栈中,线程安全
GC 影响Slot 中的引用影响 GC 的可达性判断

理解局部变量表,再结合前文说的「线程私有区域」,就能清楚理解为什么局部变量天然线程安全,而 static 变量却需要考虑并发问题。

下一节,我们来看 操作数栈(栈顶缓存/字节码指令分析)

基于 VitePress 构建