Java 的垃圾回收機制(GC)讓開發者無需手動管理內存分配,大幅降低了段錯誤與記憶體耗盡風險。然而,邏輯性內存洩漏 依然頻發:對象不再被使用,卻仍被強引用持有,導致 GC 無法回收。在記憶體受限的 Android 裝置上,這極易引發 OOM(OutOfMemoryError)與 UI 卡頓。
| 類型 | 定義 | 典型例子 |
|---|---|---|
| 傳統洩漏 | 對象所有引用已釋放,但對象未被回收 | Cursor 未關閉 |
| 邏輯洩漏 | 應用不再需要對象,仍持有強引用 | Context 洩漏、靜態變量持有 Activity |
關鍵點:強引用鏈阻斷 GC 標記清除,導致對象存活於生命週期之外。
Activity 的 Context 持有 View Hierarchy、Resources 等大量記憶體引用。一旦 Context 洩漏,關聯對象連鎖洩漏,記憶體壓力指數級上升。
Activity.onDestroy() 是生命週期終點,若此後堆中仍有強引用指向該 Activity,GC 將無法回收。
static Activity activity;
void setStaticActivity() { activity = this; }
後果:Activity 永不被回收。修正:onDestroy 置 null 或改用 WeakReference。
static View view;
void setStaticView() { view = findViewById(R.id.btn); }
風險:View 持有 Context → Context 持有 View Hierarchy 形成循環。建議:避免 static View,若必須用請在 onDestroy 釋放並置 null。
private static Object inner;
void createInnerClass() {
class InnerClass { }
inner = new InnerClass(); // 隱式持有外部類引用
}
本質:非靜態內部類實例隱式持有外部類引用。修正:改為 static 內部類 + WeakReference
new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) { while(true); }
}.execute();
場景:任務未結束前用戶退出 Activity,導致 Activity 無法銷毀。對策:onDestroy 中 cancel(true),或使用靜態內部類 + WeakReference。
new Handler().postDelayed(new Runnable() {
public void run() { while(true); }
}, Long.MAX_VALUE);
機制:MessageQueue 持有 Runnable → 持有 Activity。修正:onDestroy 移除回調,或用靜態 Handler + WeakReference。
new Thread() { public void run() { while(true); } }.start();
風險:線程未結束持有 Activity 引用。對策:標誌位控制退出,或使用 IntentService/WorkManager 替代。
new Timer().schedule(new TimerTask() {
public void run() { while(true); }
}, Long.MAX_VALUE);
同理:匿名類持有外部引用,需在 onDestroy 中 timer.cancel()。
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
後果:系統服務進程持有 Context 引用。強制要求:onDestroy 呼叫 unregisterListener(this)。
內存洩漏不僅導致 OOM,頻繁 GC 更會造成可感知的卡頓。養成「實例化時審視引用鏈、銷毀時切斷強引用」的習慣,配合工具鏈持續檢測,才能構建穩健的 Android 應用。