我遇到过一个应用程序,当异步调用 api 的方法之一使用协程作用域时,该应用程序似乎出现了内存泄漏。当我忽略它时,问题就消失了
以下 kotlin 代码重现了我在应用程序中看到的内存泄漏:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
class MyApp {
suspend fun run() {
myReport()
// while this is sleeping trigger a heapdump with visual vm
Thread.sleep(1000000000)
}
suspend fun myReport(): List<String> = runGaqlRequest().map { "some keyword" }
suspend fun runGaqlRequest(): List<String> = io {
listOf("A".repeat(300_000_000)) // very large string using 80% of a 1024mb heap
}
suspend inline fun <R> io(crossinline body: suspend () -> R): R =
coroutineScope { async(Dispatchers.IO) { body() }.await() }
}
fun main() {
val app = MyApp()
runBlocking {
app.run()
}
}
runGaqlRequest
方法模拟从API端点读取响应。这里它将返回一个包含单个元素的列表。一个巨大的 300.00.000 字符串
myReport
方法将此列表中的每个元素转换为字符串“some keywords”,因此该函数的输出是一个包含单个元素“some keywords”元素的列表
我从
myReport
方法调用 run
,然后它会休眠很长一段时间
此时,我希望巨大的字符串有资格进行垃圾收集,但是当我使用 VisualVM 创建进程的堆转储时,我看到它仍然存在并且无法被垃圾收集
当我将 runGaqlRequest 方法更改为此时
suspend fun runGaqlRequest(): List<String> {
return listOf("A".repeat(300_000_000)) // very large string using 80% of a 1024mb heap
}
因此它不使用 coroutineScope,问题就消失了。
这可能在于您拥有
suspend
功能。没有它还会有问题吗?
fun run() {
myReport()
// while this is sleeping trigger a heapdump with visual vm
Thread.sleep(1000000000)
}
...
inline fun <R> io(crossinline body: suspend () -> R): R =
runBlocking { coroutineScope { async(Dispatchers.IO) { body() }.await() } }
(虽然我不确定它是否有帮助,但其背后的想法是,由于
suspend
关键字,run
方法会等待 coroutineScope
,因此可能会因某种原因阻止垃圾收集。)