Why does this Activity still leak after rotation despite a WeakReference?
After a screen rotation the old Activity is never collected, even though the Job holds it through a WeakReference. Explain the leak and fix it without removing the WeakReference as the only change.
class MainActivity : AppCompatActivity() {
inner class Job(private val ref: WeakReference<MainActivity>) {
fun run() = Thread { /* work with ref.get() */ }.start()
}
fun doJob() = Job(WeakReference(this)).run()
}
Diagnose the cause and fix it.
An inner class holds an implicit strong reference to its outer MainActivity (this$0), and the running Thread keeps the Job alive, so the old Activity stays reachable — the WeakReference is a red herring. Fix: make Job a nested, non-inner class.
- ✗Blaming the
WeakReferenceinstead of the implicitinner-class outer reference - ✗Thinking
innercarries no reference to its enclosing class - ✗Switching to
SoftReferencerather than cutting the strong outer link
- →What is the difference between an
inner classand anestedclass in Kotlin? - →Why does a long-running
Threadact as a GC root for everything it holds?
The bug
In Kotlin an inner class holds an implicit strong reference to its enclosing instance (MainActivity.this, this$0 in bytecode). The running Thread keeps the Job, and Job (being inner) keeps the old MainActivity. After rotation a new Activity is created, but the old one stays reachable through Thread → Job → MainActivity → a leak.
class MainActivity : AppCompatActivity() {
inner class Job(private val ref: WeakReference<MainActivity>) { // ❌ inner
fun run() = Thread { /* ... */ }.start() // thread keeps Job → Activity
}
}
The inner WeakReference is a red herring: the implicit inner reference is strong and bypasses it.
The fix
Make Job an ordinary nested class (drop inner). It then has no this$0 reference, and access to the Activity goes only through the WeakReference, which the GC can reclaim.
class MainActivity : AppCompatActivity() {
class Job(private val ref: WeakReference<MainActivity>) { // ✅ nested
fun run() = Thread { /* ref.get()?.let { ... } */ }.start()
}
}