Java and the JVM may leak objects.
Leaking an object on Java (or Kotlin) is done when a reference direct or indirect (via transitivity) is kept so that the Garbage Collector cannot release the memory.
What | Meaning in this snippet |
---|---|
live |
Means, cannot be garbage collected |
died |
Means, is garbage collected |
can be kill |
Means, can be garbage collected. Indeed the garbage is free to collect or not |
ø |
Means does not have "string' reference on it ("strong" to oppose to weak) |
class A
var a: A? = A()
// Here, "a" live
class A
var a: A? = A()
a = null
// Here, "a" can be killed
class A {
var b: B? = null
}
class B
var b = B()
var a: A? = A()
a!!.b = b
a = null
// Here, "a" can be killed, "b" lives
class A {
val b = B()
init {
b.setListener {
}
}
}
class B {
var listener: Listener? = null
interface Listener { fun onChanged() }
}
var a: A? = A()
a = null
// Here, "a" can be killed
Playground conclusion:
What | Leak status |
---|---|
A -> B -> C |
Until A lives, B and C live |
A ø B -> C |
Until A lives, B and C may die |
A -> B <-> C |
Until A lives, B and C live |
A ø B <-> C |
Until A lives, B and C may die |
Activity.onCreate() {
textView.setOnClickListener {
// ...
}
}
Activity.onDestroy() {
// Nothing here
}
Activity will not leak and can be garbage collected. Why?
- Because the onClickListener instantiated via the syntax
setOnClickListener { }
is a field of the textView. - So, in term of reference links, we have
Application ø Activity -> TextView -> OnClickListener -> Activity
- Why
Application ø Activity
?- Because the application does not keep strong reference on Activity otherwise it will leak. The system (and the application) communicate with the activity without the reference system but with intents.
- Why
Activity -> TextView
?- Because the textView has been "bind" so the activity has a reference to the textView
- Why
TextView -> OnClickListener
?- Because
textView.setOnClickListener {
is the same astextView.setOnClickListener(object: View.OnClickListener {
. - Because the textView is keeping this listener argument as a field (indirect direct field)
- Because
- Why
OnClickListener -> Activity
?- Because when you do that, inside the listener, you have "2 this", the listener and the activity
- Why
textView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
this // The onClickListener
this@Activity // The activity. Indeed, the anonymous instance of onClickListener is like an inner class
}
})
That means the Activity
can be killed without leaking.
And hopefully, I personnaly never see any android dev calling textView.setOnClickListener(null)
on the onDestroy to avoid leak.
Same logic for the addTextChangedListener
and every listener inside views, adapter or anything else instiated and host on activity.
Activity.onCreate() {
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.addDefaultNetworkActiveListener {
// ...
}
}
Here the activity will leak. Why? Application ø Activity <-> ConnectivityManager <- Application
Indeed, the system service are manager that will survive the Activity lifecycle.
Another example of leak to be sure we agree on it:
Activity.onCreate() {
textView.setOnClickListener(onClickListener)
}
Activity.companion object {
val onClickListener = View.OnClickListener { /* ... */ }
}
but with a onDestroy() { textView.setOnClickListener(null) }
, no more activity leak.
Fix a memory leak is not only remove reference via nullification, it's also understanding lifecycle and reference graph. Advice: test and monitor.