jvm 对象空间分配机制深度解析:指针碰撞 vs 空闲链表
在 JVM 的线性内存空间中,要给新建的对象分配内存,有两种基本分配方式:指针碰撞(Bump-the-Pointer) 和 空闲链表(Free List)。让我们深入分析这两种机制以及它们在 TLAB 中的实现方式:
1. 两种基本分配策略
1.1 指针碰撞 (Bump-the-Pointer)

- 原理 :
- 维护一个指针标记空闲空间的起始位置
- 分配新对象时只需将指针向后移动对象大小的距离
- 需要原子操作保证线程安全(如CAS)
- 优点:分配速度极快(O(1)时间复杂度)
- 缺点:要求内存空间完全连续
- 适用场景:垃圾回收器压缩内存后(如Serial, Parallel Scavenge)
1.2 空闲链表 (Free List)
graph LR
subgraph 碎片内存空间
direction LR
Used1[对象A] --> Free1[空闲块1]
Free1 --> Used2[对象B]
Used2 --> Free2[空闲块2]
end
A[对象分配请求] --> B[遍历空闲链表]
B --> C{找到合适块?}
C -->|是| D[分割或占用]
D --> E[更新链表]
E --> F[分配完成]
C -->|否| G[分配失败]
- 原理 :
- 维护一个记录所有空闲内存块的链表
- 分配时需要遍历找到足够大的空闲块
- 如果空闲块过大,可分割使用
- 优点:支持碎片内存
- 缺点:分配速度较慢(O(n)时间复杂度)
- 适用场景:垃圾回收器不支持压缩时(如CMS)
2. JVM 的实际分配策略
2.1 分配策略选择
垃圾回收器 | 分配策略 |
---|---|
Serial | 指针碰撞 + TLAB |
Parallel Scavenge | 指针碰撞 + TLAB |
ParNew | 指针碰撞 + TLAB |
CMS | 空闲链表 + TLAB |
G1 | 混合策略 + TLAB |
2.2 TLAB 的分配机制
关键结论:TLAB 内部使用指针碰撞进行分配
flowchart TD
subgraph TLAB内部分配
TLAB[TLAB内存块] --> Pointer[top指针]
NewObject[新对象] --> CheckSpace{空闲空间 >= 对象大小}
CheckSpace -->|是| Allocate[移动top指针分配]
CheckSpace -->|否| Refill[申请新TLAB]
end
- TLAB 分配原理 :
- JVM 为每个线程在 Eden 区分配一块私有缓冲区(TLAB)
- TLAB 内部使用指针碰撞 机制:
- 每个线程维护
start
、top
、end
三个指针 - 分配时简单比较
top + size <= end
- 满足条件则移动
top
指针分配对象
- 每个线程维护
- 当 TLAB 空间不足时:
- 当前 TLAB 保留为"填充区"
- 新建另一个 TLAB 继续分配
- 新TLAB从堆中申请(使用空闲链表或指针碰撞)
3. TLAB 内存管理详解
3.1 TLAB 生命周期
sequenceDiagram
participant Thread
participant JVM
participant Eden
Thread->>JVM: 对象分配请求
alt TLAB有足够空间
Thread->>TLAB: 指针碰撞分配
TLAB-->>Thread: 返回分配地址
else TLAB空间不足
Thread->>JVM: 申请新TLAB
alt Eden有连续空间
Eden->>JVM: 指针碰撞分配新TLAB
else Eden碎片化
Eden->>JVM: 空闲链表分配新TLAB
end
JVM-->>Thread: 新TLAB地址
Thread->>新TLAB: 指针碰撞分配对象
end
3.2 TLAB 分配优化
graph TB
subgraph TLAB内存结构
TLAB --> Start[起始地址]
TLAB --> Top[当前分配指针]
TLAB --> End[结束地址]
end
subgraph 优化策略
Space1[空间浪费控制] --> 通过TLABWaste限制填充区大小
Space2[自适应调整] --> 根据线程分配速率调整TLAB大小
Space3[重试机制] --> 少量空间不足时可重试
end
3.3 JVM 实际分配流程
c
// 伪代码:真实对象分配逻辑
void* allocate_object(size_t size) {
if (size > MAX_TLAB_ALLOC_SIZE) {
// 大对象直接堆分配
return heap_allocate(size);
}
if (current_tlab.available() >= size) {
// TLAB指针碰撞快速分配
void* addr = current_tlab.top;
current_tlab.top += size;
return addr;
}
// TLAB空间不足处理
if (size > current_tlab.remaining()) {
// 申请新TLAB
current_tlab = allocate_new_tlab();
if (current_tlab.available() >= size) {
// 新TLAB分配
return allocate_in_tlab(size);
}
}
// 尝试慢速分配路径
return slow_allocate(size);
}
4. 性能对比
4.1 分配速度测试
分配方式 | 耗时 (ns/对象) | 适用场景 |
---|---|---|
TLAB + 指针碰撞 | 5-10ns | 常规对象分配 |
堆指针碰撞 | 20-50ns | 年轻代整块分配 |
空闲链表 | 100-500ns | 老年代碎片化内存 |
操作系统内存分配 | 500-2000ns | 堆外内存分配 |
4.2 为什么需要 TLAB
- 问题根源:堆分配需要原子操作保证线程安全
- TLAB解决方案 :
- 每个线程独享分配缓冲区
- TLAB内部无锁分配
- 避免全局指针的竞争
- 性能提升:高并发下效率提升10倍以上
5. 生产环境建议

最佳实践:
- 监控TLAB分配情况:
XX:+PrintTLAB
- 适当增大TLAB:
XX:TLABSize=256k
(默认64k) - 限制大对象分配:避免在循环中分配大对象
- 优化对象大小:减少内存碎片
- TLAB 参数配置:
JVM参数 | 默认值 | 作用 |
---|---|---|
-XX:+UseTLAB |
true(1.4开始) | 启用TLAB |
-XX:TLABSize |
0(自动) | 初始TLAB大小 |
-XX:MinTLABSize |
8KB | 最小TLAB大小 |
-XX:TLABRefillWasteFraction |
64 | 浪费空间阈值 |
6.完整的对象内存分配流程
flowchart TD
A[新建对象] --> B{对象超大?}
B -->|是| C[直接分配到老年代]
B -->|否| D{是否启用TLAB?}
D -->|是| E[优先在TLAB分配]
D -->|否| F[在堆Eden区分配]
E --> G[TLAB空间足够?]
G -->|是| H[TLAB内快速分配]
G -->|否| I{启用分配重试?}
I -->|是| J[在堆Eden区慢速分配]
I -->|否| K[分配失败触发GC]
H --> L[对象创建完成]
J --> L
K -->|GC后| M[重新尝试分配]
M -->|成功| L
M -->|失败| N[OOM异常]
7.对象内存分配要点
7.1 对象预筛选
graph LR
Object[新建对象] --> SizeCheck{对象大小}
SizeCheck -->|小对象| Normal[常规分配]
SizeCheck -->|大对象| DirectOld[直接进入老年代]
Normal --> RegionCheck{区域选择}
RegionCheck --> TLAB[线程本地缓存]
RegionCheck --> Eden[堆Eden区]
7.2 分配优先级(三级分配机制)
sequenceDiagram
Thread->>TLAB: 尝试在TLAB分配
alt TLAB空间足够
TLAB->>Thread: 快速分配成功
else TLAB空间不足
Thread->>Eden: 尝试Eden分配
alt Eden空间足够
Eden->>Thread: 分配成功
else Eden空间不足
Eden->>GC: 触发Young GC
GC->>Eden: 清理空间
alt GC后空间足够
Eden->>Thread: 分配成功
else GC后仍不足
GC->>Heap: 触发Full GC
alt Full GC后足够
Heap->>Thread: 分配成功
else
Heap->>Thread: 抛出OOM
end
end
end
end
7.3 TLAB技术加持
graph TB
TLAB[TLAB机制] --> Purpose[目的]
Purpose --> 减少锁竞争
Purpose --> 加速分配
TLAB --> Working[工作原理]
Working --> Step1[每个线程独立缓冲区]
Working --> Step2[优先在TLAB分配]
Working --> Step3[空间不足申请新TLAB]
TLAB --> Structure[内存结构]
Structure --> Start[起始指针]
Structure --> Top[当前分配指针]
Structure --> End[结束指针]
8.对象在堆中的典型布局
graph LR
Eden[Eden区] --> Obj1[对象A]
Eden --> TLAB1[线程1 TLAB]
Eden --> TLAB2[线程2 TLAB]
Eden --> Obj2[对象B]
TLAB1 --> T1Obj1[对象C]
TLAB1 --> T1Obj2[对象D]
TLAB1 --> TLAB1Free[空闲空间]
TLAB2 --> T2Obj1[对象E]
TLAB2 --> T2Free[空闲空间]