androidengineers.Book a session

Memory Management

Weak/Soft References: When/Why

article20 minHard

Java/Kotlin's reference types control when the GC can collect an object. Using the right reference type in the right place prevents both memory leaks and premature collection.

Reference Types Overview

ReferenceGC behaviorAndroid use case
Strong (normal)Never collected while referencedEverything by default
WeakReferenceCollected at next GC cycleObserver patterns, caches where stale is OK
SoftReferenceCollected only when heap is lowMemory-sensitive caches
PhantomReferenceCollected; get() always returns nullResource cleanup (advanced)

WeakReference: Breaking Retain Cycles

Use WeakReference when a long-lived object needs to reference a short-lived one without preventing its collection.

// ❌ AsyncTask (conceptual) holds strong reference — Activity leaked if task runs long
class MyTask(private val activity: MyActivity) : AsyncTask<Unit, Unit, Result>() {
    override fun onPostExecute(result: Result) {
        activity.updateUI(result)  // might run after Activity destroyed
    }
}

// ✅ WeakReference — if Activity is destroyed, reference is null, we bail out safely
class MyTask(activity: MyActivity) : AsyncTask<Unit, Unit, Result>() {
    private val activityRef = WeakReference(activity)

    override fun onPostExecute(result: Result) {
        activityRef.get()?.updateUI(result)  // null-safe: Activity may be gone
    }
}

WeakReference in Callback Registrations

class EventBus {
    // Store listeners as WeakReferences so they don't prevent GC
    private val listeners = mutableListOf<WeakReference<EventListener>>()

    fun register(listener: EventListener) {
        listeners.add(WeakReference(listener))
    }

    fun dispatch(event: Event) {
        val iterator = listeners.iterator()
        while (iterator.hasNext()) {
            val listener = iterator.next().get()
            if (listener == null) {
                iterator.remove()  // clean up collected references
            } else {
                listener.onEvent(event)
            }
        }
    }
}

SoftReference: Memory-Sensitive Cache

SoftReference is suitable for caches where the cached object can be reconstructed but you want to keep it as long as memory allows.

class SoftBitmapCache {
    private val cache = HashMap<String, SoftReference<Bitmap>>()

    fun put(key: String, bitmap: Bitmap) {
        cache[key] = SoftReference(bitmap)
    }

    fun get(key: String): Bitmap? {
        val ref = cache[key] ?: return null
        val bitmap = ref.get()
        if (bitmap == null) cache.remove(key)  // GC already collected it
        return bitmap
    }
}

Warning: In practice, SoftReference on Android can be too aggressive. The Dalvik/ART VM may clear soft references earlier than expected. Prefer LruCache with a fixed size for production bitmap caches — it's more predictable.

When NOT to Use Weak/Soft References

ScenarioBetter approach
ViewModel referencing ActivityNever — ViewModel must not hold Views
Background thread → UI updatelifecycleScope; it cancels on destroy
Coroutine holding ContextUse applicationContext in non-UI coroutines
Cache where correctness mattersLruCache with explicit eviction

Common Pitfall: WeakReference + Coroutine

// ❌ The WeakReference may be null by the time the coroutine resumes
suspend fun doWork(activity: MyActivity) {
    val weakRef = WeakReference(activity)
    delay(5000)
    weakRef.get()?.updateUI()  // might be null and confusing
}

// ✅ Use lifecycleScope — it cancels automatically when Activity is destroyed
class MyActivity : AppCompatActivity() {
    fun startWork() = lifecycleScope.launch {
        delay(5000)
        updateUI()  // if Activity is destroyed, scope is cancelled — no crash
    }
}

Key Takeaways

  • Use WeakReference to break retain cycles between long-lived objects and short-lived ones
  • Use it in listener/callback registrations where the listener is also the registrant's lifecycle
  • Prefer lifecycleScope/viewModelScope over WeakReference for coroutines — they're cleaner
  • Use SoftReference only for caches where you want heap-pressure-driven eviction; prefer LruCache for deterministic sizing
  • Always null-check weakRef.get() — it can become null at any GC cycle

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Weak/Soft References: When/Why | Android System Design | Android Engineers