问题
JVM如何保证给对象分配内存过程的线程安全?
答案
核心概念
JVM在多线程环境下创建对象时,需要保证内存分配的线程安全。主要通过两种机制实现:CAS(Compare-And-Swap)操作和TLAB(Thread Local Allocation Buffer)。这两种机制在保证线程安全的同时,最大化了分配性能。
线程安全机制
1. CAS + 失败重试
基本原理:使用CAS原子操作来更新内存分配指针,失败时进行重试。
// 简化的内存分配伪代码
public class MemoryAllocator {
private AtomicLong allocationPointer = new AtomicLong(startAddress);
public long allocate(int size) {
while (true) {
long current = allocationPointer.get();
long newPointer = current + size;
// CAS操作:如果当前值没变,就更新为新值
if (allocationPointer.compareAndSet(current, newPointer)) {
return current; // 分配成功,返回起始地址
}
// CAS失败,其他线程已经修改了指针,继续重试
}
}
}
CAS操作特点:
- 原子性:CPU级别保证的原子操作
- 无锁:避免了传统锁的性能开销
- 自旋:失败时重试,适合短时间竞争
2. TLAB(Thread Local Allocation Buffer)
核心思想:为每个线程在Eden区分配一块私有内存,线程优先在自己的TLAB中分配对象。
// TLAB分配机制示例
public class TLABAllocator {
// 每个线程都有自己的TLAB
private static ThreadLocal<ByteBuffer> threadLocalBuffer =
ThreadLocal.withInitial(() -> ByteBuffer.allocate(1024 * 16)); // 16KB TLAB
public Object allocate(int size) {
ByteBuffer tlab = threadLocalBuffer.get();
if (tlab.remaining() >= size) {
// 在TLAB中快速分配,无需同步
int position = tlab.position();
tlab.position(position + size);
return createObject(tlab.array(), position, size);
} else {
// TLAB空间不足,在Eden区CAS分配
return allocateInEden(size);
}
}
}
TLAB工作机制:
public class TLABWorkflow {
public static void main(String[] args) {
// 线程启动时分配TLAB
new Thread(() -> {
// 1. 在TLAB中分配对象(快速路径)
for (int i = 0; i < 1000; i++) {
Object obj = new Object(); // 大部分在TLAB中分配
}
// 2. TLAB用完后,在Eden区CAS分配(慢速路径)
Object largeObj = new LargeObject(); // 可能触发Eden区分配
}).start();
}
}
详细分配流程
快速分配路径(TLAB)
// JVM内部TLAB分配流程
public class TLABAllocation {
public Object allocateFast(int size) {
Thread current = Thread.currentThread();
TLAB tlab = current.getTLAB();
// 检查TLAB是否有足够空间
if (tlab.hasEnoughSpace(size)) {
// 快速分配,无锁操作
return tlab.allocate(size);
}
// TLAB空间不足,进入慢速路径
return allocateSlow(size);
}
}
慢速分配路径(Eden区CAS)
// Eden区CAS分配流程
public class EdenAllocation {
public Object allocateSlow(int size) {
// 1. 尝试重新分配TLAB
if (tryRefillTLAB(size)) {
return allocateInTLAB(size);
}
// 2. 在Eden区CAS分配
return allocateInEdenWithCAS(size);
}
private Object allocateInEdenWithCAS(int size) {
while (true) {
long currentTop = edenTop.get();
long newTop = currentTop + size;
// 检查Eden区是否有足够空间
if (newTop > edenEnd) {
// Eden区不足,触发Young GC
youngGC();
continue;
}
// CAS更新分配指针
if (edenTop.compareAndSet(currentTop, newTop)) {
return createObjectAt(currentTop, size);
}
// CAS失败,重试
}
}
}
JVM参数配置
TLAB相关参数
# TLAB大小(默认是Eden区的1%)
-XX:TLABSize=256k
# TLAB占用Eden区的最大比例
-XX:TLABWasteTargetPercent=1
# TLAB最大大小
-XX:MaxTLABSize=1m
# 启用/禁用TLAB(默认启用)
-XX:+UseTLAB
-XX:-UseTLAB
内存分配策略参数
# 使用偏向锁(影响TLAB refill策略)
-XX:+UseBiasedLocking
# GC线程数(影响并发分配性能)
-XX:ParallelGCThreads=4
# 使用压缩普通对象指针(影响TLAB大小)
-XX:+UseCompressedOops
性能分析与优化
1. TLAB性能优势
public class TLABPerformance {
public static void main(String[] args) {
long startTime = System.nanoTime();
// 大量小对象分配(TLAB友好)
for (int i = 0; i < 1_000_000; i++) {
new Object(); // 大部分在TLAB中分配
}
long endTime = System.nanoTime();
System.out.println("TLAB allocation time: " + (endTime - startTime) + " ns");
}
}
TLAB优势:
- 无锁分配:避免CAS竞争
- 局部性好:TLAB在CPU缓存中命中率更高
- GC友好:TLAB中的对象生命周期相近
2. TLAB vs 直接分配性能对比
public class AllocationComparison {
public static void testTLABAllocation() {
// 小对象频繁分配(TLAB优化效果好)
long start = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
new SmallObject();
}
System.out.println("TLAB allocation: " + (System.currentTimeMillis() - start) + "ms");
}
public static void testDirectAllocation() {
// 大对象分配(直接在Eden区CAS分配)
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
new LargeObject(); // 超过TLAB大小
}
System.out.println("Direct allocation: " + (System.currentTimeMillis() - start) + "ms");
}
}
3. 线程竞争分析
public class ThreadContentionAnalysis {
public static void main(String[] args) throws InterruptedException {
int threadCount = Runtime.getRuntime().availableProcessors();
CountDownLatch latch = new CountDownLatch(threadCount);
// 多线程并发分配对象
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < 1_000_000; j++) {
new Object(); // TLAB减少线程竞争
}
latch.countDown();
}).start();
}
latch.await();
}
}
源码分析
HotSpot中的TLAB实现
// 简化的HotSpot TLAB分配源码
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
// 检查空间是否足够
if (pointer_delta(top(), end()) >= size) {
HeapWord* obj = top();
set_top(obj + size); // 快速指针移动,无需同步
return obj;
}
return NULL; // 空间不足
}
// TLAB refill策略
HeapWord* CollectedHeap::allocate_from_tlab(Thread* thread, size_t size) {
HeapWord* obj = thread->tlab().allocate(size);
if (obj != NULL) {
return obj;
}
// TLAB空间不足,尝试refill
return allocate_from_tlab_slow(thread, size);
}
面试要点总结
- 两种机制:CAS操作 + TLAB技术
- CAS特点:原子性、无锁、自旋重试
- TLAB优势:线程私有、无锁分配、缓存友好
- 分配策略:优先TLAB,失败时CAS分配到Eden区
- 性能优化:TLAB减少99%的同步操作
- 参数调优:
-XX:TLABSize、-XX:TLABWasteTargetPercent
关键理解:
- TLAB是JVM为每个线程预分配的私有缓冲区
- 大部分对象在TLAB中分配,无需考虑线程安全
- 只有TLAB用完时才使用CAS操作,大大减少竞争
- 这种设计在保证线程安全的同时,最大化了分配性能
理解这个机制有助于:
- 分析多线程内存分配性能
- 调优JVM参数提升创建对象效率
- 理解垃圾回收对内存分配的影响
- 设计高性能的内存密集型应用