Skip to content

堆概述(唯一性/对象创建/GC)

堆:Java 对象的「主战场」

堆是 JVM 运行时数据区中最大的一块内存区域,也是 GC 的主战场。几乎所有通过 new 创建的对象和数组都分配在这里。

理解堆的结构和行为,是掌握 Java 内存管理和性能调优的基础。

堆的三个核心特点

1. 线程共享

堆是被所有线程共享的区域。这意味着堆中分配的对象可以被所有线程访问——这也意味着并发安全问题必须考虑

java
public class HeapSharing {
    // 这个对象在堆中,被所有线程共享
    private static final List<String> sharedList = new ArrayList<>();

    public static void main(String[] args) {
        // 100 个线程同时修改 sharedList
        for (int i = 0; i < 100; i++) {
            new Thread(() -> sharedList.add("item")).start();
        }
        // 需要考虑并发安全:ArrayList 不是线程安全的
    }
}

2. GC 管理

堆中的对象生命周期由 GC 管理。GC 会自动识别不再使用的对象并回收其内存,这就是 Java 区别于 C++ 的「自动内存管理」。

java
public class GCManaged {
    public static void main(String[] args) {
        createObjects();
        // createObjects() 中创建的对象不再被引用
        // GC 会自动回收它们占用的堆内存
    }

    static void createObjects() {
        for (int i = 0; i < 1000; i++) {
            Object obj = new Object();  // 分配在堆上
            // 方法结束后,局部变量 obj 失效
            // 但 obj 指向的 Object 实例是否被回收,取决于是否还有其他引用
        }
    }
}

3. 物理上不连续

堆是逻辑上连续、物理上可以是不连续的内存空间。现代操作系统的虚拟内存机制使这一点成为可能——JVM 从 OS 申请一段虚拟地址空间,映射到物理内存,可以跨多个物理页。

堆是唯一的

每个 JVM 进程只有一个堆。这个堆被所有线程共享。

┌─────────────────────────────────────────┐
│           JVM 进程                         │
│                                         │
│  ┌─────────────────────────────────┐   │
│  │           唯一的堆                │   │
│  │  ┌───────────┬─────────────┐  │   │
│  │  │ 年轻代     │   老年代     │  │   │
│  │  │ Eden S0 S1│              │  │   │
│  │  └───────────┴─────────────┘  │   │
│  └─────────────────────────────────┘   │
│                                         │
└─────────────────────────────────────────┘

堆与栈的对比

这是面试中最经典的问题:

维度堆(Heap)虚拟机栈(Stack)
存储内容对象实例、数组方法调用、局部变量
线程关系线程共享线程私有
大小-Xms/-Xmx 设置,通常较大-Xss 设置,通常较小(1MB)
异常OutOfMemoryError(堆溢出)StackOverflowError/OOM(栈溢出/线程数过多)
GC需要垃圾回收器管理无需 GC,线程结束即回收
分配方式从堆上分配(大多数情况下很快)栈上分配(比堆更快)
灵活性固定或动态大小固定大小

对象创建的完整过程

当你写 new Object() 时,JVM 内部经历了什么?

new Object()


1. 检查常量池:Object 类是否已加载?

      │ 否 → 类加载(Loading → Linking → Initialization)


2. 分配内存:在堆上为 Object 实例分配空间

      │  分配方式:指针碰撞 / 空闲列表
      │  并发处理:CAS / TLAB


3. 零值初始化:instance 实例字段设为默认值


4. 设置对象头:Mark Word + 类型指针


5. 执行构造器:<init> 方法


6. 返回对象引用

关于对象创建的详细字节码过程,我们会在后续的 字节码视角:对象创建过程 中深入讲解。

堆的分代结构

现代 JVM 的堆通常分为「年轻代」和「老年代」两个区域:

┌──────────────────────────────────────────────────────┐
│                    堆(Heap)                        │
│                                                      │
│  ┌────────────────────┐  ┌─────────────────────┐ │
│  │       老年代(Old Generation)       │ │
│  │  长期存活的对象 / 大对象     │ │
│  │  容量:约堆的 2/3                  │ │
│  └────────────────────┘  └─────────────────────┘ │
│                                                      │
│  ┌──────────────────────────────────────────┐ │
│  │             年轻代(Young Generation)           │ │
│  │                                                │ │
│  │  ┌──────────────────────────────────────┐ │ │
│  │  │       Survivor S1  │    Survivor S0   │ │ │
│  │  │     (空白)       │   (使用中)       │ │ │
│  │  └──────────────────────────────────────┘ │ │
│  │  ┌──────────────────────────────────────┐ │ │
│  │  │              Eden 区                  │ │ │
│  │  │       新对象在这里分配                  │ │ │
│  │  └──────────────────────────────────────┘ │ │
│  └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

分代设计基于一个重要的经验观察:大多数对象的生命周期很短。通过把新对象放在年轻代并频繁回收,GC 可以只扫描少量数据,大幅提升效率。

关于分代的详细结构和对象流转过程,我们会在后续章节深入讲解。

堆溢出(OutOfMemoryError: heap space)

当堆无法为新对象分配空间时,会抛出 OutOfMemoryError: Java heap space

java
public class HeapOOM {
    public static void main(String[] args) {
        // 不断创建对象,直到堆耗尽
        List&lt;Object&gt; list = new ArrayList&lt;&gt;();
        while (true) {
            list.add(new byte[1024 * 1024]);  // 每次分配 1MB
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at HeapOOM.main(HeapOOM.java:8)

排查方法

  • 添加 -XX:+HeapDumpOnOutOfMemoryError,生成堆转储文件
  • 用 MAT/JProfiler 分析 dump,找出占用最大的对象
  • 检查是否有内存泄漏(大对象无法回收)

本节小结

堆是 JVM 中最重要的内存区域:

  • 线程共享:所有线程共用一个堆
  • GC 管理:自动回收不再使用的对象
  • 分代设计:年轻代(Eden + Survivor)+ 老年代
  • 核心异常OutOfMemoryError: Java heap space

理解堆,是理解 Java 内存管理的第一步。接下来,我们来看 堆细分结构/大小设置/OOM案例

基于 VitePress 构建