Reputation: 1095
I have encountered an application that appears to be suffering from a memory leak when one of the methods that asynchronously calls an api uses a coroutineScope. The problem goes away when i omit it
The following piece of kotlin code reproduces a memory leak that I am seeing in an application:
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()
}
}
The runGaqlRequest
method simulates reading a response from an API endpoint. Here it will return a list with a single element in it. A huge 300.00.000 character string
The myReport
method transforms each element in this list to the string "some keyword" so the output of this function is a list with a single element "some keyword" element
I invoke myReport
from a run
method, that then goes to sleep for a very long time
At this point I would expect the huge string to be eligible for garbage collection however when I use visualvm to create a heapdump of the process i see its still there and unable to be garbage collected
When I change the runGaqlRequest method to this
suspend fun runGaqlRequest(): List<String> {
return listOf("A".repeat(300_000_000)) // very large string using 80% of a 1024mb heap
}
So it does not use the coroutineScope the problem goes away.
Edit: I created this repository that can reproduce the problem
https://github.com/jelmerk/reproduce-kotlin-problem
Upvotes: 3
Views: 666
Reputation: 2719
Just try with
rootView.lifecycle().repeatOnLifecycle(Lifecycle.State.STARTED) {
flow.collect { item -> ... }
}
Or if you inside fragment of activity then just lifecycle().repeatOnLifecycle(Lifecycle.State.STARTED)...
without rootView
.
Upvotes: 0