局部变量表槽位按变量首次使用字节码顺序分配而非源码行序,32位类型占1槽、long/double占2连续槽,this固定占槽0,对象引用存于槽中而对象本身在堆;槽位在作用域结束后可被复用。

栈内存中的局部变量表不是按源码行序分配的,而是由编译器根据字节码指令流中变量首次被使用的顺序来确定槽位(Slot)索引。每个槽默认32位宽,基本类型和引用类型都以槽为单位存入,但对象本身绝不在栈上——只存线索。
槽位怎么算:宽度与占用规则
局部变量表的容量在编译期就固定,由方法的 max_locals 属性决定。槽位分配不看“写了几行”,而看“哪条字节码第一次用到它”:
- 32位类型占1个槽:boolean、byte、char、short、int、float,以及所有引用类型(String、ArrayList、自定义对象等)
- 64位类型占2个连续槽:long 和 double,且高位对齐(访问时只需指定起始槽索引)
-
未初始化的局部变量不占有效语义槽:如
int a;若后续未赋值就使用,编译器直接报错;若赋值后再用,才正式占用槽 - byte/short/boolean 实际也转成 int 存储:它们在局部变量表中不保留原始宽度,统一按 int 处理(逻辑值不变)
this 和参数的槽位位置是固定的
非静态方法的局部变量表,前几个槽永远有明确归属:
- 槽 0 固定存放 this 引用(当前对象实例的地址)
- 后续槽按方法参数声明顺序依次分配:例如
void m(int x, String s),x 占槽 1,s 占槽 2 - 方法体内定义的变量,从下一个空闲槽开始分配,不跳过已分配但已失效的槽
对象引用存在哪儿?为什么不是对象本身?
局部变量声明如 User u = new User();,局部变量表中只存一个值——指向堆中那个 User 实例起始地址的引用:
- 这个引用在 HotSpot JVM 中通常是32位压缩指针(开启 UseCompressedOops 时),或64位原生地址(未开启或堆过大)
- 它不是句柄:现代 HotSpot 默认用直接指针,不经过句柄表间接寻址
-
User u = null;对应槽中为全 0 比特模式,即逻辑上的空指针 - 对象本体(包括字段、数组元素、对象头等)全部在堆内存中分配,与栈帧生命周期无关
槽位复用:调试时“变量突然消失”的原因
局部变量表是定长数组,无法扩容,但 JVM 允许复用已退出作用域的槽:
- if 块内声明的
String s = "a";,一旦 if 执行完,s 的槽就可被后续变量重用 - for 循环中每次迭代新建的局部变量(如
int i),很可能反复使用同一个槽 - 这种复用不影响程序语义,但调试器可能无法在作用域外显示该变量——不是 bug,是空间优化










