栈帧未弹出前局部变量引用始终有效,对象不可回收;方法返回栈帧弹出、引用失效后才进入可回收候选状态,但实际回收取决于gc触发时机与作用域。

调用栈执行期间,GC不会因为“某个方法正在运行”就暂停或禁止回收;但局部变量是否还有效,直接决定对象能否被回收——而这个有效性,由栈帧的存续状态决定。
栈帧不退出,引用就一直有效
每个方法调用都会在Java虚拟机栈中压入一个栈帧,其中存放局部变量表。只要该方法没返回(包括未执行完、卡在循环里、等待锁、或处于try-finally中间),栈帧就持续存在,里面的引用变量就构成一条强可达路径。
哪怕你写了 obj = null,也只是清空了这个局部变量的值,并没有让栈帧消失。JVM仍认为该对象“可能被后续代码使用”,因此不会仅凭这行赋值就判定它可回收。
- 递归调用时,外层方法的栈帧仍在,其引用的对象依然不可回收
- 方法内有synchronized块或long-running I/O操作,栈帧保持活跃,引用持续生效
- 即使方法已执行到最后一行return语句前,只要还没真正弹出栈帧,引用就有效
方法返回才是引用失效的关键节点
当方法执行完毕并返回时,对应栈帧被弹出,所有局部变量(含对象引用)同时失效。此时,如果该对象再无其他强引用(如静态字段、线程本地变量、其他活动栈帧中的引用等),它才真正进入“可回收候选”状态。
注意:失效 ≠ 立即回收。是否真的被回收,取决于下一次GC何时触发、以及该次GC是否扫描到它。
- Minor GC只扫描新生代,若对象在老年代,需等到下次老年代GC或Full GC
- 即使栈帧已弹出,若对象被放入ThreadLocal、缓存、监听器列表等长期持有结构中,仍不可回收
- 可通过 -XX:+PrintGCDetails 观察GC日志,结合对象晋升行为反推栈帧清理是否及时
GC可能在任何方法执行中途发生
GC是JVM全局行为,不受单个方法生命周期约束。只要满足触发条件(如Eden区满),Minor GC随时可能发生——哪怕当前正处在某个复杂计算方法的第1000次循环中。
此时JVM会执行Stop-the-World暂停,所有Java线程(包括该方法的执行线程)停在当前字节码位置,等GC完成后再继续。
- 频繁创建临时对象的方法,容易引发高频Minor GC,拖慢整体执行速度
- 长时间运行且分配大量对象的方法,可能在执行中遭遇Full GC,导致明显卡顿
- 栈深度大(如深层递归)本身不触发GC,但可能加剧老年代压力(因局部引用长期持有可能导致对象无法晋升)
避免误判“对象该被回收了”
开发者常以为“方法里把引用设为null”或“离开作用域括号”就等于对象可回收,这是典型误解。JVM只认栈帧是否弹出、强引用链是否断裂、GC是否运行这三个硬条件。
调试时若发现对象迟迟不被回收,优先检查:是否被静态集合持有?是否注册了未注销的回调?是否在ThreadLocal中泄漏?而不是怀疑“为什么方法都结束了还不收”。
- 使用 jstack 查看线程栈帧,确认目标方法是否真已退出
- 用 jmap -histo 或 MAT 分析堆快照,定位强引用链源头
- 开启 -XX:+HeapDumpOnOutOfMemoryError 捕获内存溢出时的现场











