源本科技 | 码上会

Java 对象的存储方式

2026/01/23
11
0

学习目标

  • 理解 Java 对象在堆内存中的分配机制

  • 掌握对象引用与实际对象的存储位置区别(堆 vs 栈)

  • 了解 Java 堆的内部结构(新生代、老年代、Metaspace)

  • 熟悉对象的内存布局(对象头、实例数据、对齐填充)

  • 了解主流垃圾回收器的特点与适用场景


对象存储的基本原则

在 Java 中,所有通过 new 关键字创建的对象实例都存储在堆内存中,而指向这些对象的引用变量则存储在栈内存中

Student s = new Student("Alice", 20);
  • new Student(...) 创建的对象 → 存储在 堆(Heap)

  • 引用变量 s → 存储在 栈(Stack),其值为堆中对象的地址

注意:仅声明类类型的变量(如 Student s;不会创建对象,只会创建一个未初始化的引用(默认为 null)。


堆内存

核心作用

堆是 JVM 中最大的一块内存区域,用于动态分配所有对象实例和数组。

堆的内部划分

区域

说明

新生代
(Young Generation)

新创建的对象首先分配在此。
包含 Eden 区和两个 Survivor 区(S0、S1)。大多数对象在此“朝生夕死”。

老年代
(Old Generation)

经历多次 GC 后仍存活的对象会被晋升至此。
对象生命周期较长。

元空间
(Metaspace)(Java 8+)

替代旧版 PermGen,存储类元数据(如类结构、方法字节码、静态变量等),
使用本地内存(Native Memory),不受堆大小限制。

在 Java 7 及更早版本中,类元数据存储在 永久代(PermGen),位于堆内,容易因类加载过多导致 OutOfMemoryError: PermGen space。Java 8 移除 PermGen,改用 Metaspace,显著提升了类加载的灵活性和稳定性。

垃圾回收

堆是垃圾回收器(GC)的主要工作区域。JVM 自动识别并回收不再被引用的对象,释放内存。


栈内存

核心特点

  • 线程私有:每个线程拥有独立的栈

  • 自动管理:方法调用时压入栈帧,返回时自动弹出

  • 存储内容

    • 局部变量(包括基本类型如 int, double

    • 方法参数

    • 对象引用(注意:不是对象本身!)

public static void main(String[] args) {
    int n = 10;               // 基本类型 → 栈
    Student s = new Student("Bob", 20); // 引用 s → 栈;对象 → 堆
    calculate(n);             // 参数 n 的副本 → 栈
}

public static int calculate(int n) {
    int ans = n * 10;         // 局部变量 → 栈
    return ans;
}

栈内存分配和释放速度极快,但容量有限。深度递归可能导致 StackOverflowError


对象的内存布局

每个 Java 对象在堆中由三部分组成:

对象头

Object Header

  • Mark Word:包含哈希码、GC 分代年龄、锁状态(偏向锁、轻量级锁等)、线程 ID 等

  • Class Pointer:指向对象所属类的元数据(在 Metaspace 中)

实例数据

Instance Data

  • 对象的实际字段(成员变量)

  • 字段顺序可能被 JVM 重排以优化内存对齐

对齐填充

Padding

  • 保证对象总大小为 8 字节的倍数(HotSpot 要求),提升 CPU 访问效率

开发者可通过 jdk.internal.misc.Unsafe 或第三方工具(如 JOL - Java Object Layout)查看对象内存布局。


垃圾回收器

Garbage Collectors

JVM 提供多种 GC 算法,适用于不同应用场景:

垃圾回收器

特点

适用场景

Serial GC

单线程,Stop-The-World

小型应用、客户端模式

Parallel GC
(默认至 Java 8)

多线程并行回收,吞吐量优先

后台计算、批处理

CMS
(Concurrent Mark-Sweep)

并发标记清除,低延迟

Web 应用、响应时间敏感系统(已废弃)

G1 GC
(Java 9+ 默认)

分区回收,可预测停顿时间

大堆(>4GB)、低延迟要求

ZGC

暂停时间 <1ms,支持 TB 级堆

超大堆、极致低延迟

Shenandoah

与 ZGC 类似,OpenJDK 社区维护

同上

选择 GC 策略需权衡 吞吐量 停顿时间


最佳实践

  1. 避免过早优化:优先编写清晰代码,再根据性能分析调整

  2. 合理设置堆大小:通过 -Xms / -Xmx 避免频繁扩容

  3. 监控 GC 行为:使用 jstat, VisualVM, GC logs 分析停顿与回收效率

  4. 减少短生命周期大对象:避免直接进入老年代

  5. 谨慎使用 static 集合:易导致内存泄漏(对象无法被回收)


思考题

  1. 如果一个对象只被局部变量引用,当方法执行完毕后,该对象是否立即被回收?为什么?

  2. 在 Java 8 之后,字符串常量池(String Pool)存储在堆还是 Metaspace?为什么这样设计?

  3. 对象头中的“锁信息”是如何支持 synchronized 关键字实现的?偏向锁、轻量级锁、重量级锁分别在什么场景下升级?