JVM通过多种机制保证对象内存分配的线程安全性,以下是主要技术:
- TLAB(Thread Local Allocation Buffer)- 首选方案
每个线程在堆的Eden区预分配一小块私有内存区域:
java
// 伪代码示意
class Thread {
private byte[] tlab; // TLAB内存区域
private int tlabCursor; // 当前分配指针
private int tlabSize; // TLAB大小
Object allocate(size) {
if (tlabCursor + size <= tlabSize) {
Object obj = tlab + tlabCursor; // 直接指针碰撞
tlabCursor += size;
return obj; // 无需同步
}
return allocateSlowPath(size); // TLAB不足,走慢路径
}
}
· 优点:大部分小对象分配无竞争
· TLAB大小:通过-XX:TLABSize调整,默认动态自适应
- CAS + 指针碰撞(Bump-the-Pointer)
当TLAB用完或分配大对象时使用:
java
// 伪代码 - 指针碰撞分配
class HeapAllocator {
private volatile long freePointer; // 堆空闲指针
Object allocateAtomic(size) {
while (true) {
long oldPtr = freePointer;
long newPtr = oldPtr + size;
if (newPtr > heapEnd) {
return null; // 触发GC
}
// CAS更新指针
if (compareAndSwap(freePointer, oldPtr, newPtr)) {
return (Object)oldPtr; // 分配成功
}
// CAS失败则重试
}
}
}
- 堆分区与锁分离
G1/Shenandoah等现代收集器:
java
// G1的Region分配策略
class G1Allocator {
// 每个Region有独立的分配指针
Region[] regions;
Object allocate(size) {
// 1. 尝试当前活跃Region
Region region = currentRegion.get();
if (region.allocateTLAB(size)) {
return result;
}
// 2. CAS竞争新的Region
while (true) {
Region newRegion = atomicGetNewRegion();
if (newRegion != null) {
currentRegion.set(newRegion);
return newRegion.allocate(size);
}
}
}
}
- 不同GC算法的具体实现
Serial/ParNew GC(Eden区分配):
· ParNew:多线程并行收集,但分配时每个线程有独立指针
· 通过-XX:+UseParNewGC启用
CMS GC:
· 使用自由列表管理老年代
· 分配时需要同步访问空闲链表
java
class FreeListAllocator {
Block freeListHead;
ReentrantLock lock = new ReentrantLock();
Object allocate(size) {
lock.lock();
try {
// 遍历空闲链表找到合适块
Block block = findBlock(size);
splitBlock(block, size);
return block.address;
} finally {
lock.unlock();
}
}
}
G1 GC:
· 每个Region独立管理
· 使用PLAB(Promotion Local Allocation Buffer)提升并行度
- 内存分配的完整流程
java
// 简化的分配流程
Object allocateMemory(size_t size) {
// 1. 尝试TLAB快速分配
if (size <= TLAB_MAX_SIZE) {
Object obj = tryTLABAllocate(size);
if (obj != null) return obj;
}
// 2. TLAB不足,申请新的TLAB
if (needNewTLAB(size)) {
TLAB newTLAB = allocateNewTLAB(); // 需要同步
if (newTLAB != null) {
setCurrentTLAB(newTLAB);
return newTLAB.allocate(size);
}
}
// 3. 直接堆分配(大对象或TLAB分配失败)
if (size > HUGE_THRESHOLD) {
return allocateHugeObject(size); // 老年代直接分配
} else {
return allocateFromHeap(size); // CAS指针碰撞
}
}
- 关键JVM参数
参数 作用
-XX:+UseTLAB 启用TLAB(默认开启)
-XX:TLABSize 设置初始TLAB大小
-XX:+ResizeTLAB 允许动态调整TLAB大小
-XX:TLABRefillWasteFraction TLAB重填浪费比例
-XX:-DoEscapeAnalysis 关闭逃逸分析(影响栈分配)
-
性能优化建议
-
调整TLAB大小:避免频繁重填
bash-XX:TLABSize=256k -
监控工具:
bash# 查看TLAB分配情况 jstat -gc <pid> 1000 # 详细诊断 -XX:+PrintTLAB -XX:+PrintPromotionFailure -
减少竞争:
· 适当增加Eden区大小
· 避免过度线程数
· 使用-XX:+UseNUMA优化多路CPU
总结
JVM通过分层策略保证分配安全:
- TLAB:处理95%以上的小对象分配,无锁
- CAS原子操作:处理TLAB外的普通分配
- 区域锁/细粒度锁:管理空闲链表等复杂结构
- 逃逸分析:栈上分配完全避免堆竞争
这种设计在安全性和性能间取得了良好平衡,使得Java能在高并发下高效分配内存。