Kotlin实现Worker Pool时长运行协程引发Java堆OOM如何解决
Kotlin实现Worker Pool触发OutOfMemoryError的修复方案
问题根因
OOM的核心诱因不是常驻工作协程持有对象引用,而是两点设计缺陷:
- 带缓冲的Channel容量设置严重不合理。你给存图片
ByteArray的Channel设了10000的缓冲容量,图片属于典型大对象,单张从几百KB到数MB不等,一旦接口请求的生产速度超过5个worker的消费速度,待处理的任务会直接在缓冲队列里堆积,这些堆积的Pair<String,ByteArray>被Channel强引用,GC完全无法回收,很快就会打满Java堆内存。 - 资源生命周期没有绑定,且Channel直接暴露存在误用风险。协程作用域没有和服务生命周期绑定销毁,Channel直接设为public对外暴露,没有做发送逻辑封装,也没有背压保护,很容易出现无限制往队列塞任务的情况。
常驻协程本身不会持续持有已经处理完的对象引用:只要处理逻辑没有在循环外部持有任务对象的引用,单次任务处理完成退出方法后,局部变量的引用会自动断开,GC可以正常回收对应内存。
修复方案
- 收缩Channel缓冲容量,依赖Channel原生的挂起机制实现背压。大对象场景下缓冲容量设置为5~20即可,缓冲打满时
send方法会自动挂起发送方,不会无限堆积待处理任务。 - 封装Channel操作,不要将Channel直接暴露为public属性。协程作用域和服务生命周期绑定,服务销毁时主动关闭Channel、取消协程,避免资源泄露。
- 去掉多余的协程嵌套,用
Channel.consumeEach实现安全的消费循环,Channel关闭时协程会自动退出,不会出现死循环泄露。 - IO密集型的上传任务不要用
Dispatchers.Default调度器,替换为Dispatchers.IO更适配场景,避免挤占CPU密集型任务的调度资源。
修正后代码
ServiceA实现
import jakarta.annotation.PostConstruct import jakarta.annotation.PreDestroy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch class ServiceA { // 大对象场景下调小缓冲容量,靠挂起机制做背压,避免任务堆积 private val channel = Channel<Pair<String, ByteArray>>(capacity = 10) private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) @PostConstruct fun createWorkerGroup(){ // 移除多余的外层协程嵌套,直接启动固定数量worker repeat(5) { workerId -> coroutineScope.launch { println("Create Worker $workerId") // consumeEach会在Channel关闭时自动退出循环,避免协程泄露 channel.consumeEach { task -> uploadImage(task) } } } } // 封装任务提交方法,不对外暴露Channel本身 suspend fun submitUploadTask(url: String, imageBytes: ByteArray) { channel.send(url to imageBytes) } private suspend fun uploadImage(urlAndImage: Pair<String, ByteArray>){ val (url, image) = urlAndImage println("Uploading Image: $url") // 实际上传逻辑在此处实现 // 方法退出后,url、image的局部引用自动断开,GC可正常回收内存 } // 服务销毁时主动释放资源 @PreDestroy fun destroy() { channel.close() coroutineScope.cancel() } }
Controller层调用调整
// 替换原有直接操作Channel的逻辑,通过封装方法提交任务 uploadService.submitUploadTask(url, image.bytes)
补充说明:如果处理的是超大体积的图片字节数组,可以在上传逻辑完成后手动将对应变量置空,主动断开引用,帮助GC更快回收内存,常规体积的图片不需要额外处理。
内容的提问来源于stack exchange,提问作者tschuehly
相关产品推荐
相关产品推荐

