协程内存消耗未低于JVM线程?我的基准测试是否有误?
我参考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,但我从测试图中未发现二者内存使用有明显差异,请问我的基准测试设计是否存在问题?
基准测试设计的核心问题
你的测试没有触发协程内存优势的核心场景,主要问题如下:
线程池线程数量过少
你使用newFixedThreadPool(12),最多仅创建12个线程。每个Java线程默认栈内存约1MB,12个线程的栈总内存仅约12MB。而4000个协程的内存开销主要是协程状态对象(每个仅几百字节),总内存也在几MB级别,两者内存规模接近,自然看不到明显差异。
要体现差异,应该让线程池创建与协程数量相当的线程(比如4000个),此时线程栈总内存会达到4GB左右,而协程的内存开销仍维持在几MB,差异会非常显著。使用阻塞IO而非异步IO
你的doGet方法使用HttpURLConnection这种阻塞式IO,即便是协程运行该任务,线程也会被阻塞(协程调度器无法在IO等待时复用线程)。此时协程并没有真正发挥"轻量"的优势——只有当协程执行非阻塞IO(比如使用Ktor异步HTTP客户端)时,才会在IO等待时释放线程,让线程可以处理其他协程,从而大幅降低线程数量,体现内存优势。测试环境未隔离
你在同一个进程中先后运行协程和线程池测试(注释掉了其中一个),但JVM的内存管理会有缓存、垃圾回收等因素干扰,导致内存测量不准确。正确的做法是分别单独运行两个测试,确保每次测试的JVM初始状态一致。协程调度器选择的影响
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

