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

协程内存消耗未低于JVM线程?我的基准测试是否有误?

线程池与Kotlin协程内存占用基准测试的问题分析

我参考Stack Overflow的内容完成了一项基准测试,用于对比线程池线程与Kotlin协程的内存使用情况,测试代码如下:

val COUNT = 4_000

val executor: Executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())// 我的机器是12核
fun main(array: Array<String>)  = runBlocking{
    val latch = CountDownLatch(COUNT)
    val start = System.currentTimeMillis()
    repeat(COUNT) {
        launch(Dispatchers.Default) {
            testByCoroutine(latch)
        }
    }
    latch.await()
    println("total: " + (System.currentTimeMillis() - start))

    // testByThreadPool()
}

fun testByThreadPool() {
    val latch = CountDownLatch(COUNT)
    val start = System.currentTimeMillis()
    for (i in 0..<COUNT) {
        executor.execute {
            val num: Int = request1()
            println(request2(num))
            latch.countDown()
        }
    }
    try {
        latch.await()
    } catch (e: InterruptedException) {
        throw RuntimeException(e)
    }
    println("total: " + (System.currentTimeMillis() - start))
    exitProcess(0)
}

fun testByCoroutine(latch: CountDownLatch) {
    val num = request1()
    val res: Int = request2(num)
    println(res)
    latch.countDown()
}

fun request1(): Int {
    return doGet("https://bing.com")
}

fun request2(token: Int): Int {
    val response: Int = doGet("https://bing.com")
    return response + token
}

fun doGet(link: String): Int {
    try {
        val url = URL(link)
        val conn = url.openConnection() as HttpURLConnection
        conn.setRequestMethod("GET")
        conn.setConnectTimeout(3000)
        return conn.getResponseCode()
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
    }
    return -1
}

测试结果

  • 线程池场景总耗时:518218ms
  • Dispatchers.Default场景总耗时:561510ms
  • Dispatchers.IO场景总耗时:262719ms

从对应的内存占用图中,未发现线程池与协程的内存使用有明显差异。

根据Stack Overflow的内容,协程并非为更快速度设计,而是为更低内存消耗,另有基准测试显示线程与协程的内存消耗比约为6:1,但我从测试图中未发现二者内存使用有明显差异,请问我的基准测试设计是否存在问题?


基准测试设计的核心问题

你的测试没有触发协程内存优势的核心场景,主要问题如下:

  1. 线程池线程数量过少
    你使用newFixedThreadPool(12),最多仅创建12个线程。每个Java线程默认栈内存约1MB,12个线程的栈总内存仅约12MB。而4000个协程的内存开销主要是协程状态对象(每个仅几百字节),总内存也在几MB级别,两者内存规模接近,自然看不到明显差异。
    要体现差异,应该让线程池创建与协程数量相当的线程(比如4000个),此时线程栈总内存会达到4GB左右,而协程的内存开销仍维持在几MB,差异会非常显著。

  2. 使用阻塞IO而非异步IO
    你的doGet方法使用HttpURLConnection这种阻塞式IO,即便是协程运行该任务,线程也会被阻塞(协程调度器无法在IO等待时复用线程)。此时协程并没有真正发挥"轻量"的优势——只有当协程执行非阻塞IO(比如使用Ktor异步HTTP客户端)时,才会在IO等待时释放线程,让线程可以处理其他协程,从而大幅降低线程数量,体现内存优势。

  3. 测试环境未隔离
    你在同一个进程中先后运行协程和线程池测试(注释掉了其中一个),但JVM的内存管理会有缓存、垃圾回收等因素干扰,导致内存测量不准确。正确的做法是分别单独运行两个测试,确保每次测试的JVM初始状态一致。

  4. 协程调度器选择的影响

    • Dispatchers.Default是为CPU密集型任务设计的,默认线程数等于CPU核心数(你的机器是12),和线程池的线程数一致,此时协程的并发执行本质上还是依赖12个线程,内存开销自然和线程池接近。
    • Dispatchers.IO默认线程数上限更高(通常是CPU核心数*64),所以耗时更短,但线程数仍远低于4000,内存优势依然无法体现。

优化后的测试建议

  • 调整线程池配置:使用newCachedThreadPool(无上限线程数)或者直接创建4000个线程的线程池,触发大量线程的内存开销。
  • 替换为异步IO:使用Ktor、OkHttp的异步API等非阻塞IO库,让协程在IO等待时真正挂起,不占用线程。
  • 隔离测试环境:每次仅运行一种测试,重启JVM后再执行另一种。
  • 增加并发数:将COUNT提升到10000甚至更高,放大内存差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 06:05:00