主题
Java 对象通常怎样分配,垃圾收集器又如何判断对象可以回收?
题型:追问 · 区分语言语义、JIT 优化和 HotSpot 的具体实现。
建议回答
对象在语义上创建于 Java 堆,但具体实现会先看对象能否被标量替换或消除;真正需要分配时,线程通常优先在自己的 TLAB 中通过指针碰撞快速分配,TLAB 不够再走共享分配路径。大对象、分配失败后的处理以及是否直接进入老年代取决于收集器、堆布局和 JVM 参数,不能概括成固定规则。
GC 不依赖引用计数,而是从 GC Roots 做可达性分析。常见根包括线程栈中的活动引用、静态字段、JNI 引用和 JVM 内部引用。一个对象从根不可达后才具备被回收的条件;软、弱、虚引用还会按各自语义在引用处理阶段参与判断。
设计边界
- 栈上分配通常是逃逸分析和标量替换带来的优化结果,不是 Java 规范承诺。
finalize已被弃用,资源释放应使用try-with-resources、显式关闭或 Cleaner 等受控机制。- “不可达”不等于业务上立刻释放,实际回收时间由收集器周期决定。
进一步追问
- 分代假设为什么有效,跨代引用如何避免扫描整个老年代?
- 并发标记期间引用关系变化,收集器怎样避免漏标?
易错点
- 不要把所有新对象都说成一定先进入 Eden,也不要把大对象去向说成所有收集器都一致。
- 不要认为循环引用会导致 Java GC 无法回收;只要整组对象从 GC Roots 不可达,就可以被回收。