Has anyone seen this memory leak happening? It's c...
# announcements
b
Has anyone seen this memory leak happening? It's causing an out of memory closure on our Android App every ~2 days or so. https://youtrack.jetbrains.com/issue/KT-34125
k
I'm having trouble understanding, so the first and second lambda leak but the third one doesn't?
b
The first lambda does NOT leak, as it is statically instantiated. The second and third lambdas DO leak, as they are instantiated at run time.
s
I understand why the 2nd and 3rd one are not statically instantiated; they capture variables from another non-static environment (properties of a Main instance and local var of a non-static Main method). Seeing the 'NEW' in the bytecode does not surprise me. NEW instantiations are never released explicitly by (byte)code. Instead, the garbage collector picks them up when they are no longer referenced. If you see an out-of-memory exception or a growing heap in this example code, put some
System.gc()
statements in there to strongly suggest the GC cleans up some memory. If the out-of-memory still happens (or the heap won't settle to a value), something seems to be really wrong with the garbage collector. For Single<T>; if you capture non-static context in its chain, be sure to unsubscribe/dispose/cancel any subsdcription to it. If not, you may leak the lambdas.
b
I attempted calling
System.gc()
, which did not help the garbage collector collect the lambdas. We use the Android WorkManager with RxWorker in our app, which should handle disposing the reactive streams for us. I attempted explicitly releasing the disposables from our reactive streams, which also did not help.
I was able to fix the leak by making all of our lambdas statically instantiated instead of NEWly instantiated. Makes sense given the example in the bug report posted above. It seems that kotlin is keeping a reference to NEWly instantiated lambdas, preventing them from being garbage collected.
s
We don’t see this in our code, and we have plenty of lambdas capturing outer instances. As long as the outer instance is not leaked, the lambda is not leaked (the lambda itself must be released appropriately (e.g. dispose of Rx subscriptions, cancelation of CoroutineScopes, etc)). I wonder what is different….
b
I believe I have found the source of our issue. Part of our chain was a method like the following
Copy code
private fun waitForThing(desiredThing: String? = null): Single<Boolean> {
    var disposable: Disposable? = null
    return Single.create { emitter: SingleEmitter<Boolean> ->
        disposable = currentThing.subscribeBy(
                onNext = { currentConnectedThing: String ->
                    if (desiredThing == currentConnectedThing) {
                        emitter.onSuccess(true)
                    } else if (desiredThing == null && currentConnectedThing != "Unknown") {
                        emitter.onSuccess(true)
                    }
                })
    }.timeout(30, TimeUnit.SECONDS)
            .onErrorReturnItem(false)
            .doFinally {
                disposable?.dispose()
            }
}
Having the Single emitter referenced in another subscription seemed to cause the whole chain to leak.
Fixing that one method allows other lambdas in the chain to be allocated and deallocated by the gc at runtime.
So the issue I posted above is still an issue, just not the cause of the memory leak in our application.