https://kotlinlang.org logo
Title
d

Daniele Segato

10/22/2018, 3:58 PM
According to many difference sources on the web
by lazy
can cause memory leaks. Notable one: https://twitter.com/chrisbanes/status/897075635142754305 Is this an old issue that has been solved in recent versions of kotlin or is it still a "gotcha" that the developer has to take care of? If still a gotcha, any suggestion on how to handle values that might not be initialized? As far as I understand I can't access the delegate directly.
m

Martin Devillers

10/22/2018, 3:59 PM
It’s not a language issue, so it hasn’t changed.
👍 2
1
The problem lies in loading
by lazy
some value which depends on a mutable state
In this case the
view
d

Daniele Segato

10/22/2018, 4:02 PM
I'm not using it for views. I use it on stuff that doesn't change during the lifespan of the object but is not available when the object is constructed (aka = activity or fragment in Android)
and I'm concerned of memory leaks
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}
it does
initializer = null
after using it
before that it holds an indirect reference to the class containing the
by lazy
.
m

Martin Devillers

10/22/2018, 4:08 PM
The problem presented in the Tweet that you shared isn’t about the initializer leaking anything, it’s about the loaded value which is leaking a whole view hierarchy which is obsolete
e

Egor Trutenko

10/22/2018, 4:09 PM
The root of the problem is that people actually mistake lazy initialization to late initialization, which might seem the same thing, but apparently not in Kotlin
m

Martin Devillers

10/22/2018, 4:10 PM
Simply put: - Fragment is created - Field is accessed, lazily loaded from the view - Fragment is re-created, therefore an entirely new view is created - When accessing the field, it still uses the value that it had loaded from the previous view, rather than re-loading it from the previous one
@Egor Trutenko I don’t know what you mean
d

Daniele Segato

10/22/2018, 4:12 PM
Martin if the view is recreated on the same fragment after the by lazy has already produced a value I completely agree it shouldn't use the new view. but if the fragment is re-created shouldn't the by lazy be an entirely new object?
m

Martin Devillers

10/22/2018, 4:13 PM
Not necessarily, Fragment instances can be detached and re-attached, effectively recreating their view, without being destroyed
The joys of the fragment lifecycle 🙂
e

Egor Trutenko

10/22/2018, 4:15 PM
I mean, why even try to initialize views via lazy instances?
lazy
is not meant for that,
lateinit
is
d

Daniele Segato

10/22/2018, 4:15 PM
well yeah of course in that case I wouldn't use by lazy unless it is not strictly related to stuff that doesn't change on the same fragment
m

Martin Devillers

10/22/2018, 4:17 PM
Both tools are valid and have different consequences. Using
lateinit
usually means that you’re loading all your view references at creation, which is something which was initially discouraged because of the overhead (although I’d argue that it’s generally insignificant on modern devices). Using
lazy
means that your view references are loaded only when they’re needed.
d

Daniele Segato

10/22/2018, 4:17 PM
I'm not using by lazy for views, I use the kotlin view binding extension. That's just the example written by Chris Banes, my question was about memory leaks
m

Martin Devillers

10/22/2018, 4:17 PM
Like I said, memory leaks are an issue if
lazy
is loading a value which depends on a mutable state
d

Daniele Segato

10/22/2018, 4:17 PM
and I just want to know if thee by lazy can cause memory leaks
mutable state has nothing to do with memory leaks
m

Martin Devillers

10/22/2018, 4:18 PM
It has everything to do with memory leaks…
d

Daniele Segato

10/22/2018, 4:19 PM
a memory leak is caused by something holding a reference to an object that is supposed to have been garbage collected
a

arekolek

10/22/2018, 4:19 PM
even if you use a
lateinit var
, or a nullable
var
you can recreate the same memory leak from the tweet
e

Egor Trutenko

10/22/2018, 4:20 PM
Yes, but only if you try hard to More complicated instruments, like
lazy
, are discouraged to apply to poorly architectured frameworks (Android), because of such unexpected behavior and leaks.
m

Martin Devillers

10/22/2018, 4:20 PM
I’m well aware of what a memory leak is. And the issue with memory leaks is because the state has mutated therefore some instances should be collected.
d

Daniele Segato

10/22/2018, 4:22 PM
say. Activity X, instance A and B a
by lazy
in A is never initialized (not used). The initialization lambda hold a reference to instance A. When A gets destroyed and B created I expect the by lazy to go away and not keep A alive. B has it's own by lazy. Is that true?
m

Martin Devillers

10/22/2018, 4:23 PM
Explained differently: instances loaded by
lazy
will have the same memory lifecycle as the instance in which they were created. Ask yourself whether that’s what your want or not. If the block of
lazy
depends on some state of the outer instance which is going to mutate, then the answer is no.,
d

Daniele Segato

10/22/2018, 4:24 PM
then there's the other case: Fragment Y same thing but it survive between A and B. In this case an uninitialized
by lazy
should only have a reference to the fragment and thus shouldn't leak activity A. when it is initialized it MIGHT leak activity A depending on what value has been initialized to. This is how I expect it to work
m

Martin Devillers

10/22/2018, 4:25 PM
I’m not sure I understand your exemple, but if you have a
lazy
field in a component like an
activity
, then if that activity gets destroyed & garbage collected then the lazy instance will also get released, lambda & value.
d

Daniele Segato

10/22/2018, 4:26 PM
perfect, then it works as I expected and means by lazy doesn't leak anything.
m

Martin Devillers

10/22/2018, 4:26 PM
You’re describing an edge case which really shouldn’t be relied on. It depends on how lambdas capture their class properties, which I’m not sure of.
d

Daniele Segato

10/22/2018, 4:27 PM
?
m

Martin Devillers

10/22/2018, 4:27 PM
In your second case, with the fragment which hasn’t loaded the value yet
I think it will access the property again through the getter, so it should “work”, but it would still be terrible code
d

Daniele Segato

10/22/2018, 4:31 PM
private val viewModel: MyViewModel by lazy {   ViewModelProviders.of(requireActivity()).get(MyViewModel::class.java)
    }
the initialization need the activity (
requireActivity()
) to initialize but the
MyViewModel
itself has no reference that leak the activity or the fragment. how is this terrible code?
the lambda initializer has a reference to my fragment in this case.
Martin Devillers do you mind telling me why you call that code terrible?
r

ribesg

10/23/2018, 7:35 AM
Please don’t blame your ignorance of how a feature works on the feature.
m

Martin Devillers

10/23/2018, 8:25 AM
I mean a code that would rely on a
lazy
property not having been already initialized when calling it would be terrible
Regarding your example, if your view model can only be used by the activity, then you’re fine. If it’s likely to be used by a different activity, then you’ll likely have a problem.