compose-multiplatform: Memory leak in SnapshotStateObserver?

Hi JB team,

I’m trying to get up with a desktop app which updates small part of UI quite frequently (e.g. a timer). As result of app’s normal operation, memory consumption grows relatively quickly. Profiling points to SnapshotStateObserver, which seem doesn’t release some state. However, I don’t see a way how to control that or set a limit.

After just half an hour of work, memory trend is clear: image

On below screenshot you may see Live Bytes and Objects, the max numbers there constantly grow: image

Sample app source:

import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import kotlinx.datetime.Clock
import kotlin.concurrent.fixedRateTimer

fun main() = application {
    remember {
        fixedRateTimer(period = 50) { Holder.refreshClock() }
    }

    Window(onCloseRequest = ::exitApplication) {
        Text(Holder.clock.value)
    }
}

object Holder {
    val clock = mutableStateOf(Clock.System.now().toString())

    fun refreshClock() {
        clock.value = Clock.System.now().toString()
    }
}

OS: Manjaro Linux Compose: 1.1.0 OpenJDK: 11.0.15

Looking forward hearing back from you. Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 16 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks to all for investigating this issue! And especially @pema4 for the fix.

It is fixed, and the fix will be in 1.3.0 (or in 1.2.2, we will decide soon).

I believe the problem is in the UpdateEffect composable

On every invocation of the performUpdate snapshotObserver is subscribed with new lambda object:

fun performUpdate() {
    snapshotObserver.observeReads(
        Unit,
        onValueChangedForScope = { tasks.trySend(::performUpdate) }
    ) {
        currentUpdate()
    }
}

I replaced this declaration with the following and the memory leak disappeared:

lateinit var onValueChangedForScope: (Unit) -> Unit
fun performUpdate() {
    snapshotObserver.observeReads(
        Unit,
        onValueChangedForScope = onValueChangedForScope,
    ) {
        currentUpdate()
    }
}
onValueChangedForScope = { tasks.trySend(::performUpdate) }

I posted the following already at the google issue tracker:

The memory leak is still present in Compose 1.2.0 with Kotlin 1.7.20. I profiled the code below (https://github.com/Ic-ks/compose-memory-leak) for 3 minutes with the Java Flight Recorder (the related .jfr file can be found here: https://issuetracker.google.com/action/issues/223222717/attachments/39334890?download=true). The overall allocated memory was more than 7GB. Heap usage steadily increased to 80 MB. It seems that SnapshotStateObserver does not clean its references properly.

val randomValueFlow = flow { while (true) emit(Math.random() * 100) }
    .onEach { delay(10) }
    .map { it.toInt() }
    .flowOn(Dispatchers.Default)

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        val randomValue by randomValueFlow.collectAsState(0L)
        Text(text = randomValue.toString(), fontSize = 20.sp)
    }
}

jfr

PS: .jfr files can be viewed with JDK Mission Control (https://github.com/openjdk/jmc)

Thanks for pointing at this issue, we’ll take a look.

There is a related bug report for Jetpack Compose: https://issuetracker.google.com/issues/223222717

This appears similar to a problem I have been investigating, though my problem occurs on Android with Jetpack Compose. Some details here

I think I may be experiencing the same issue. My app slows down after time, so I started the Intellij debug view. After running the app for some time I paused it and took a look at the memory usage. I can see that there are lots of allocations of androidx.compose.runtime.collection.IdentityArraySet. See screenshot: Screenshot 2022-04-22 at 14 31 57

Is this expected? I am not sure. When looking at the stack trace of one of the objects I see the following:

Screenshot 2022-04-22 at 14 32 39