一个 Java 对象从创建到被回收,经历了哪些过程?
结论先行:一个普通对象的主线是“类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行构造方法 → 正常使用 → 失去引用 → GC 判定不可达 → 回收”。其中“创建”由 new 指令驱动,“死亡”由可达性分析判定,中间还包含 finalize 逃生机会等细节。
关键阶段梳理
- 类加载检查:new 指令先定位常量池中的符号引用,若类尚未加载则触发加载、连接、初始化。
- 分配内存:根据 GC 是否带压缩整理选择“指针碰撞”或“空闲列表”;优先走 TLAB(线程本地分配缓冲),避免并发分配冲突。
- 内存清零:对分配到的空间做零值初始化,保证实例字段不赋初值也有默认值(0、false、null)。
- 设置对象头:写入 hashCode、GC 分代年龄、锁状态等 Mark Word 信息与指向类元数据的类型指针。
- 执行构造:调用 <init> 完成用户初始化,并把对象引用压入栈,对象进入“可达”的正常生命周期。
- 等待回收:引用链断开后对象不可达,经可达性分析与两次标记后进入回收,内存被释放并可供复用。
- 年龄晋升:对象每熬过一次 Minor GC 年龄 +1,达到阈值(默认 15)后晋升老年代,避免长期占用新生代。
回收前的两次标记
不可达对象先被第一次标记,若未覆写 finalize 或已执行过则直接回收;否则进入 F-Queue 等待低优先级线程执行 finalize,执行后仍需第二次标记确认,仍未自救才被回收。
提醒:finalize 已标记废弃且执行时机不受控;资源清理应实现 AutoCloseable 交给 try-with-resources,不要依赖它兜底。
public class Lifecycle {
private static final int SIZE = 100; // 类变量归属类,存于元空间
public static void main(String[] args) {
Lifecycle obj = new Lifecycle(); // 引用在栈,实例在堆
obj = null; // 断开引用,成为回收候选
// 后续 GC 时可达性分析发现 obj 不可达,即可回收
}
@Override
protected void finalize() { // 已标记废弃,仅供理解“逃生”机制
System.out.println("finalize 不保证执行");
}
}
常见追问 / 记忆点
- 记忆点:创建靠“加载→分配→清零→对象头→构造”,死亡靠“不可达→两次标记→回收”。
- 追问:finalize 不保证执行且拖慢回收,已被标记废弃,不要用它“救活”对象。
- 追问:可达不等于立即回收,JVM 会按分代策略选择时机(如 Minor GC / Full GC)统一处理。
- 追问:想观察创建与回收动作,可用 -XX:+PrintGCDetails 看日志,晋升与回收都会留下记录。