一个 Java 对象从创建到被回收,经历了哪些过程?

结论先行:一个普通对象的主线是“类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行构造方法 → 正常使用 → 失去引用 → GC 判定不可达 → 回收”。其中“创建”由 new 指令驱动,“死亡”由可达性分析判定,中间还包含 finalize 逃生机会等细节。

关键阶段梳理

  1. 类加载检查:new 指令先定位常量池中的符号引用,若类尚未加载则触发加载、连接、初始化。
  2. 分配内存:根据 GC 是否带压缩整理选择“指针碰撞”或“空闲列表”;优先走 TLAB(线程本地分配缓冲),避免并发分配冲突。
  3. 内存清零:对分配到的空间做零值初始化,保证实例字段不赋初值也有默认值(0、false、null)。
  4. 设置对象头:写入 hashCode、GC 分代年龄、锁状态等 Mark Word 信息与指向类元数据的类型指针。
  5. 执行构造:调用 <init> 完成用户初始化,并把对象引用压入栈,对象进入“可达”的正常生命周期。
  6. 等待回收:引用链断开后对象不可达,经可达性分析与两次标记后进入回收,内存被释放并可供复用。
  7. 年龄晋升:对象每熬过一次 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 看日志,晋升与回收都会留下记录。
笔记加载中…