You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用JMH测试含CompletableFuture并行逻辑的Java方法时遇线程OOM问题

解决JMH测试含并行逻辑代码时的线程创建OOM问题

你在使用JMH测试build方法性能时,该方法内部通过CompletableFuture创建多线程并行加载内存对象Dictionary,但当前的基准方法每次迭代或预热都会重复执行,导致抛出java.lang.OutOfMemoryError: unable to create new native Thread错误。以下是针对这类场景的最佳测试实践:

1. 复用线程池,避免线程爆炸

LocalFSDictionaryCreator的build方法每次执行时,不要每次都创建新线程或依赖无界线程池(比如默认的ForkJoinPool.commonPool在高并发调用下也会过载)。应该在类中预先初始化固定大小的线程池,所有并行任务复用该池中的线程:

public class LocalFSDictionaryCreator implements DictionaryCreator {
    // 根据CPU核心数设置合理的线程池大小
    private static final ExecutorService FIXED_POOL = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    @Override
    public Dictionary build() {
        // 并行任务指定使用复用的线程池
        CompletableFuture<Void> loadTask1 = CompletableFuture.runAsync(this::loadMetadataPart1, FIXED_POOL);
        CompletableFuture<Void> loadTask2 = CompletableFuture.runAsync(this::loadMetadataPart2, FIXED_POOL);
        // 等待所有任务完成
        CompletableFuture.allOf(loadTask1, loadTask2).join();
        return assembleDictionary();
    }

    // 其他加载和组装逻辑...
}

2. 复用DictionaryCreator实例,避免重复初始化资源

当前基准方法每次调用都会新建LocalFSDictionaryCreator实例,这会重复初始化线程相关资源甚至文件句柄。使用JMH的@State注解管理实例生命周期,确保整个测试周期内只初始化一次:

@State(Scope.Benchmark)
public class DictionaryBenchmark {
    private DictionaryCreator creator;

    // 每个测试批次(Trial)前执行一次初始化
    @Setup(Level.Trial)
    public void initCreator() {
        creator = new LocalFSDictionaryCreator("metadata_avro_files");
    }

    @Benchmark
    public Dictionary buildDictionary() {
        return creator.build();
    }
}

@State(Scope.Benchmark)保证实例在整个基准测试中唯一,@Setup(Level.Trial)确保初始化逻辑只在测试批次开始时执行,避免重复创建对象带来的资源浪费。

3. 合理配置JMH的测试参数

  • 控制测试线程数:如果使用@Threads注解设置JMH测试线程,要确保测试线程数 + 内部并行线程数不超过系统可承受的线程总数,比如先设置@Threads(1)做单线程基准测试,验证稳定性后再调整。
  • 调整预热和迭代次数:减少不必要的预热迭代,比如设置@Warmup(iterations = 3, time = 1)(3次预热,每次1秒),避免过多预热导致线程资源耗尽。示例配置:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Threads(1)
@Fork(1)
@State(Scope.Benchmark)
public class DictionaryBenchmark {
    // ... 测试代码
}

4. 确保资源正确清理

如果build方法中打开了文件流、临时连接等资源,要在方法执行完毕后及时关闭,避免资源泄漏间接引发内存或线程问题。也可以通过JMH的@TearDown注解在测试结束后统一清理资源:

@TearDown(Level.Trial)
public void cleanup() {
    // 关闭线程池或其他资源
    ((LocalFSDictionaryCreator) creator).shutdownPool();
}

内容的提问来源于stack exchange,提问作者Ravi Kapoor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:52:43