批处理中使用Kotlin协程出现Java堆内存不足问题排查
问题分析与解答
1. 堆内存耗尽的原因
你的代码核心问题是无限制并发:
- 对列表中的每个元素直接启动一个协程,完全没做并发数量限制。如果列表元素量级很大(比如上万条),会瞬间创建大量协程,每个协程都要占用栈内存、状态对象等资源,再加上所有API返回的结果都存放在内存中,直接把堆内存撑爆。
awaitAll()会等待所有协程执行完成,所有结果都会被保留在内存里,如果单个API返回的数据量不小,会进一步加剧内存压力。- 额外问题:你在函数内部创建了独立的
CoroutineScope,这个scope没有和调用方的runBlocking上下文绑定,既无法被外部取消,也没有合理的并发控制逻辑。
2. 异常时的协程资源情况
当某个API调用抛出异常时:
- 你在
async代码块里捕获异常后又重新抛出,该协程会进入失败状态,但其他正在运行的协程不会被自动取消——因为你创建的CoroutineScope没有绑定失败协程的取消逻辑,这些协程会继续执行直到自身完成(成功或抛出异常)。 - 只有当这些协程执行完成后,它们占用的堆内存资源才会被GC回收;如果协程因API调用卡住而长期阻塞,相关资源会一直占用内存。
优化参考
要解决内存问题,核心是加并发限制,同时优化协程上下文绑定:
suspend fun <T, V> Iterable<T>.customAsyncAll(method: suspend (T) -> V): List<V> = coroutineScope { this@customAsyncAll.asFlow() .flatMapMerge(concurrency = 10) { // 限制并发数为10,可根据实际调整 flow { emit(method(it)) } } .catch { e -> log.error { "customAsyncAll Exception: ${e.stackTraceToString()}" } throw e } .toList() }
- 用
coroutineScope替代独立创建的CoroutineScope,让协程和调用方上下文绑定,支持外部取消。 - 通过
flatMapMerge限制并发数,避免瞬间创建大量协程。
内容的提问来源于stack exchange,提问作者firstpostcommenter
相关产品推荐
相关产品推荐

