Java对象是如何在堆内存中分配的?

一、Java 堆内存概述

Java 虚拟机(JVM)的堆内存是用于存储对象和数组的运行时数据区域。以 HotSpot JVM 为例,堆内存通常分为以下几个部分:

  • 年轻代:包括 Eden 区和两个 Survivor 区(S0 和 S1),用于存放新创建的对象。
  • 老年代:存储生命周期较长的对象。
  • 元空间:存储类元数据、方法信息等,取代了 Java 8 前的永久代。

对象分配主要发生在年轻代的 Eden 区,这是我们关注的重点。

二、对象在 Eden 区分配

当我们使用 new 关键字创建对象时,例如:

Object obj = new Object();

JVM 会执行以下步骤:

  1. 确定内存大小:JVM 计算对象所需内存,包括对象头(存储类信息、哈希码等)、实例变量和对齐填充(通常对齐到 8 字节的倍数)。

  2. 分配内存:
    优先使用 TLAB:JVM 为每个线程分配一个线程本地分配缓冲区,对象优先在 TLAB 中分配,避免多线程竞争堆锁,提高效率。
    Eden 区分配:如果 TLAB 空间不足,对象在 Eden 区分配。JVM 使用 指针碰撞(连续内存时移动指针)或 空闲列表(碎片化内存时选择合适块)分配内存。

  3. 初始化:
    清零内存:分配的内存空间初始化为默认值(例如,0、null)。
    设置对象头:写入类元数据、分代年龄等信息。
    调用构造函数:执行 方法,完成对象初始化。

三、Eden 区满后触发 Minor GC

当 Eden 区空间不足以分配新对象时,JVM 触发 Minor GC(也叫 Young GC,YGC),清理年轻代中的无用对象。过程如下:

  1. 标记存活对象:扫描 Eden 区和 From Survivor 区(S0 或 S1)的存活对象。

  2. 复制存活对象:
    存活对象从 Eden 区和 From Survivor 区复制到 To Survivor 区(S1 或 S0)。
    如果 To Survivor 区空间不足,部分对象会直接晋升到老年代。

  3. 清理内存:清空 Eden 区和 From Survivor 区,释放无引用对象的空间。

  4. 角色互换:From Survivor 和 To Survivor 区角色互换,为下一次 Minor GC 做准备。

分代年龄:每次 Minor GC,存活对象的分代年龄(GC Age)加 1。当年龄达到阈值(默认 15,可通过 -XX:MaxTenuringThreshold 配置)时,对象晋升到老年代。

四、后续内存管理

Minor GC 后,Eden 区腾出空间,新对象继续在 Eden 区分配。以下是后续的内存管理机制:

  1. 对象晋升老年代:
  • 大对象:超过一定大小(通过 -XX:PretenureSizeThreshold 设置)的对象直接分配到老年代,避免频繁复制。
  • Survivor 区不足:To Survivor 区无法容纳所有存活对象时,部分对象晋升到老年代。
  • 年龄阈值:对象经过多次 Minor GC 后,年龄达到阈值,晋升到老年代。
  1. 老年代满后触发 Full GC:
    当老年代空间不足时,JVM 触发 Full GC,清理整个堆(年轻代 + 老年代)。
    Full GC 使用 标记-清除-整理算法,回收无用对象并整理内存碎片,耗时较长,可能导致应用暂停(Stop-The-World)。
  2. 内存分配失败:
    如果 Full GC 后仍无足够空间,JVM 抛出 OutOfMemoryError。

五、总结

Java 对象的堆内存分配和垃圾回收是一个高效且复杂的过程。对象通常在 Eden 区分配,Eden 区满后触发 Minor GC,存活对象移动到 Survivor 区,多次 GC 后可能晋升到老年代,最终通过 Full GC 清理整个堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值