面试tips--JVM(1)--对象分配内存的方式&TLAB

发布于:2025-08-30 ⋅ 阅读:(17) ⋅ 点赞:(0)

1. 对象分配的基本方式

当 Java 程序创建对象时(new Object()),JVM 需要在 堆内存 中为对象分配空间。分配方式主要有两种:

(1)指针碰撞(Bump-the-Pointer)

  • 场景:堆内存是 规整的(即所有空闲空间都集中在一侧,已使用空间在另一侧,中间用指针隔开)。

  • 机制:维护一个指针(如 top),对象分配时只需把指针向前移动一段等于对象大小的距离即可。

  • 优点:非常高效,O(1) 操作。

  • 前提:堆必须是连续且规整的,通常依赖 垃圾收集器(如复制算法、G1的Region内存整理) 来保持堆的规整性。

(2)空闲列表(Free List)

  • 场景:堆内存 不规整(存在很多分散的小空闲块)。

  • 机制:JVM 维护一个 空闲内存块列表,对象分配时从列表中找到一块足够大的空间(可能涉及首次适配、最佳适配等策略)。

  • 优点:适用于碎片化的内存。

  • 缺点:需要遍历或管理链表,性能比指针碰撞低。


2. TLAB(Thread Local Allocation Buffer,线程本地分配缓冲区)【相当于指针碰撞的优化策略】

  • 背景:在多线程环境下,如果所有线程都直接在堆上分配对象,会涉及全局指针的竞争,导致加锁开销。

  • 机制:JVM 为每个线程分配一个 小块堆内存私有区域(TLAB)

    • 每个线程优先在自己的 TLAB 中分配对象,不需要加锁,效率接近 指针碰撞

    • TLAB 内存用完时,线程再向堆申请新的 TLAB(这个申请动作可能涉及锁)。

  • 关系

    • TLAB 内部使用指针碰撞:因为 TLAB 本身是一个连续的内存块,线程在其中分配对象时,只需移动指针即可。

    • 如果 TLAB 无法满足对象大小TLAB 已满,线程才会退回到全局堆分配,此时可能使用 指针碰撞(若堆规整)或 空闲列表(若堆不规整)。

为啥要优化?

①.指针碰撞(Bump-the-Pointer)的问题
  • 原理:堆是规整的,空闲的内存空间连续排列。分配对象时,只需要把一个“分配指针”往前挪动一段距离(对象大小),就能得到新对象的内存区域,非常快。

  • 并发问题
    在多线程环境下,多个线程可能同时尝试修改这个“分配指针”。

    • 线程 A 还没来得及更新指针

    • 线程 B 也在修改指针
      → 可能导致两个对象分配到同一片内存,产生覆盖或数据错乱。


②解决方法

JVM 提供了两种方案:

(1) 加锁(CAS 或同步)
  • 对全局的分配指针操作加锁或使用 CAS,保证同一时间只有一个线程可以移动指针。也就是说是对分配内存空间的动作进行同步处理—— 实际上虚拟机是采用 CAS 配上失败 重试的方式保证更新操作的原子性;

  • 缺点:多线程竞争时会有锁开销,影响性能。

(2) 使用 TLAB(Thread Local Allocation Buffer)
  • 原理:给每个线程在 Eden 区预分配一小块私有的内存区域(TLAB)。

  • 线程在自己的 TLAB 内部分配对象时,不需要加锁,可以直接用指针碰撞的方式分配,非常快。

  • 当 TLAB 用完时,再申请新的 TLAB,才会涉及全局堆的同步操作。


3. 三者关系总结

  • 指针碰撞:最高效的分配方式,适用于规整的堆。

  • 空闲列表:适用于堆不规整,有内存碎片时使用。

  • TLAB:线程私有的分配缓冲区,本质上仍依赖 指针碰撞,是为了解决多线程竞争堆分配的问题。【相当于指针碰撞的优化策略

一句话总结
对象分配时,线程会先在自己的 TLAB(基于指针碰撞) 中分配;如果失败,则退回全局堆,全局堆会根据是否规整选择 指针碰撞空闲列表

4. 小贴士

  • 分配方式选择规则

    • 堆规整(比如新生代 Eden 区):👉 指针碰撞

    • 堆不规整(比如老年代 CMS):👉 空闲列表

  • TLAB:是 指针碰撞 的线程私有优化,优先分配;分配失败才退化到全局分配。