问题

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);
}

面试要点总结

  1. 两种机制:CAS操作 + TLAB技术
  2. CAS特点:原子性、无锁、自旋重试
  3. TLAB优势:线程私有、无锁分配、缓存友好
  4. 分配策略:优先TLAB,失败时CAS分配到Eden区
  5. 性能优化:TLAB减少99%的同步操作
  6. 参数调优-XX:TLABSize-XX:TLABWasteTargetPercent

关键理解

  • TLAB是JVM为每个线程预分配的私有缓冲区
  • 大部分对象在TLAB中分配,无需考虑线程安全
  • 只有TLAB用完时才使用CAS操作,大大减少竞争
  • 这种设计在保证线程安全的同时,最大化了分配性能

理解这个机制有助于:

  • 分析多线程内存分配性能
  • 调优JVM参数提升创建对象效率
  • 理解垃圾回收对内存分配的影响
  • 设计高性能的内存密集型应用