2026年7月11日 · 6 分钟阅读

JVM 对象与垃圾回收:对象创建、内存分配、引用类型与 GC 收集器

梳理 Java 对象创建流程、TLAB、对象进入老年代的条件、对象内存布局、可达性分析、引用类型和常见垃圾收集器。

JVM 里最核心的两件事,一件是对象怎么创建,一件是对象什么时候被回收。理解这条生命周期,就能更好地分析内存分配、GC 日志和线上内存问题。

一个对象的一生,可以粗略分成:

创建 -> 分配内存 -> 使用 -> 判定是否存活 -> 回收或晋升

对象创建流程

当代码执行 new 时,JVM 创建对象大致会经历这些步骤:

  1. 检查常量池中是否能定位到对应类的符号引用。
  2. 确认类是否已经加载、解析和初始化。
  3. 为对象分配内存空间。
  4. 将分配到的内存初始化为零值。
  5. 设置对象头信息。
  6. 执行构造方法,把对象初始化为程序期望的状态。

类没有加载完成之前,对象无法真正创建。

对象内存怎么分配

对象内存分配方式主要有两种:

  • 指针碰撞:堆内存规整时,只需要移动分界指针。
  • 空闲列表:堆内存不规整时,用列表记录哪些内存块可用。

选择哪种方式,和垃圾收集器是否会整理内存有关。如果收集器能压缩整理内存,就更容易使用指针碰撞。

分配安全问题

多线程同时创建对象时,会竞争堆内存分配位置。JVM 通常用两种方式保证安全:

  • CAS + 失败重试。
  • TLAB,也就是线程本地分配缓冲。

TLAB 会给每个线程预先分配一小块 Eden 内存。线程创建小对象时,优先在自己的 TLAB 中分配,减少线程间竞争。

对象什么时候进入老年代

对象进入老年代常见有四种情况。

情况说明
长期存活对象对象每熬过一次 Minor GC,年龄增加,达到阈值后进入老年代
大对象大数组、大字符串等可能直接进入老年代
Survivor 放不下Minor GC 后存活对象无法放入 Survivor,触发空间担保
动态年龄判断同年龄对象总大小超过 Survivor 一定比例时,年龄大于等于该值的对象提前晋升

对象晋升不是坏事,但老年代增长过快就要警惕。

内存担保机制

Minor GC 后,Eden 中存活对象通常会复制到 Survivor。如果 Survivor 放不下,就需要老年代进行空间担保,把存活对象放入老年代。

这也是为什么新生代配置过小、Survivor 配置不合理,可能导致对象过早进入老年代,进而增加 Full GC 压力。

对象内存布局

Java 对象在堆中的布局通常包括三部分:

区域说明
对象头 HeaderMark Word、类型指针,数组还会包含长度信息
实例数据 Instance Data对象的成员变量数据
对齐填充 PaddingJVM 对象通常按 8 字节对齐,不足则补齐

对象头里保存了很多运行时信息,比如哈希码、GC 分代年龄、锁状态等。synchronized 锁升级也和对象头里的 Mark Word 有关。

对象访问方式

Java 程序通过引用访问对象。常见实现有两种:

  • 句柄访问。
  • 直接指针访问。

句柄访问的好处是对象移动时只需要修改句柄中的地址,引用本身比较稳定;直接指针访问的好处是少一次指针定位,速度更快。HotSpot 主要使用直接指针。

什么是垃圾

简单说,没有被任何有效引用关联的对象,就是垃圾。

但 JVM 不能只靠引用计数判断对象是否存活,因为循环引用会让两个无用对象互相引用,引用计数都不为 0。

HotSpot 主要使用可达性分析。

可达性分析

可达性分析从 GC Roots 出发,沿着引用链向下搜索。凡是能被 GC Roots 直接或间接到达的对象,就是存活对象;不能到达的对象,就可能被回收。

常见 GC Roots 包括:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中 Native 方法引用的对象。
  • 活跃线程对象等。

四种引用类型

Java 引用类型包括强引用、软引用、弱引用和虚引用。

类型回收特点典型场景
强引用只要引用存在,一般不会被回收普通对象引用
软引用内存不足时可能被回收缓存
弱引用下一次 GC 时会被回收WeakHashMap
虚引用不影响对象生命周期,用于回收通知直接内存清理等

引用类型决定了对象和 GC 之间的关系。

垃圾清除算法

常见垃圾回收算法有三类:

  • 标记-清除:标记垃圾后直接清除,容易产生内存碎片。
  • 标记-整理:标记后把存活对象向一端移动,减少碎片。
  • 复制算法:把存活对象复制到另一块区域,适合新生代。

新生代对象大多朝生夕死,所以复制算法效率高;老年代对象存活率高,更适合标记-清除或标记-整理。

常见垃圾收集器

收集器特点
Serial单线程收集器,简单但会 Stop The World
ParNewSerial 的多线程版本,常与 CMS 搭配
Parallel Scavenge吞吐量优先的新生代收集器
Parallel OldParallel Scavenge 的老年代版本
CMS低停顿并发收集器,但会产生碎片
G1面向服务端应用,支持可预测停顿
ZGC面向低延迟、大内存场景

选择收集器时要看目标:是吞吐量优先,还是响应时间优先。

G1 的基本思路

G1 不再简单按连续的新生代、老年代管理堆,而是把堆划分为多个 Region。它会评估每个 Region 的回收价值,优先回收收益高的区域。

G1 的特点包括:

  • 并行与并发。
  • 多代收集。
  • 空间整合。
  • 可预测停顿。

它适合大多数服务端应用,但也不是万能的。更短停顿通常意味着更多额外开销。

Minor GC、Major GC 和 Full GC

类型含义
Minor GC新生代 GC
Major GC通常指老年代 GC,不同资料中定义可能有差异
Full GC整个堆和方法区相关区域的 GC

线上排查时,不要只看发生了哪种 GC,还要看触发原因、耗时、回收前后内存变化。

小结

对象和 GC 可以按这条线理解:

  1. 对象创建前先确认类已加载。
  2. 对象优先在 Eden 或 TLAB 分配。
  3. 存活对象可能进入 Survivor,再逐步晋升老年代。
  4. 可达性分析决定对象是否存活。
  5. 不同引用类型影响对象回收时机。
  6. 不同收集器服务不同目标。

理解对象生命周期之后,GC 日志就不再是一堆看不懂的数字,而是对象分配和回收行为的记录。