局部变量线程安全,是因为每个线程调用方法时独立分配私有栈帧,局部变量存于该帧中,地址隔离、互不共享;引用变量本身线程私有,但其指向的堆对象可能被多线程访问。

局部变量不会被“物理复制”,而是每个线程在调用方法时,**独立分配一块新的栈帧内存**——这本身就是物理隔离的体现。所谓“每个线程都有自己的副本”,本质不是复制已有值,而是在各自私有的栈空间里,重新开辟一段地址连续、互不重叠的内存来存放同名变量。
栈帧是线程私有的物理内存块
Java 每个线程启动时,JVM 就为其分配一块固定大小(可配置)的栈空间。这块空间由操作系统划出,起始地址、大小、访问权限都与其他线程完全隔离。当线程执行方法时,JVM 在该线程栈顶压入一个栈帧,帧内包含局部变量表、操作数栈等区域。这些内容全部落在本线程独占的物理(或更准确说是虚拟)内存页上,其他线程的 CPU 指令根本无法寻址到这一段。
- 两个线程同时调用同一个方法,会生成两个栈帧,分别位于各自栈的不同位置,地址不重合、无共享可能
- 即使局部变量是对象引用(如 String s = new String("abc")),引用本身(8 字节指针)存在栈帧里,对象实体则在堆中;但引用变量这个“容器”从不跨线程传递,只在本帧内存在
没有“复制动作”,只有“独立分配”
所谓“每个线程有自己的局部变量副本”,不是说 JVM 在某个时刻把变量从线程 A 的栈拷贝一份给线程 B。它发生在方法调用入口:线程 B 执行到该方法第一行时,JVM 立即在其栈顶分配新帧,并按方法签名初始化局部变量槽(基本类型置默认值,引用类型为 null)。这个过程与线程 A 完全无关,也不依赖其栈状态。
- 递归调用也一样:每次进入方法,都在当前线程栈上新开一帧,哪怕方法名、参数类型完全相同,帧地址也逐层下移(栈向下生长)
- 局部变量生命周期严格绑定于栈帧:帧弹出,变量立即失效;没有 GC 干预,也没有跨帧残留
对比堆区,凸显栈的隔离刚性
堆是所有线程共享的内存池,对象一旦创建,任何持有其引用的线程都可读写。而栈的物理隔离是硬性的:操作系统通过页表和 MMU 确保不同线程的栈虚拟地址映射到不同的物理页帧,连越界访问都会触发段错误(SIGSEGV)。这种底层保护机制,让栈帧天然成为线程安全的“堡垒”。
- static 变量存数据段、new 出的对象存堆——它们才需要同步;局部变量天生避开了共享路径
- JNI 调用时,Java 栈帧和 C++ 栈帧在同一线程栈上连续堆叠(RSP 连续推进),但仍是同一私有空间内的不同段,不改变隔离本质















