1. 內存泄漏是什么?
定義:內存泄漏是指程序中的對象已經不再需要,但由于被其他對象錯誤引用,導致垃圾回收器(GC)無法回收它,從而長期占用內存空間的現象。
2. 內存泄漏的危害
問題 | 具體表現 |
---|---|
內存持續增長 | 應用占用內存越來越大 |
卡頓與ANR | 頻繁GC導致界面卡頓,操作無響應 |
OOM崩潰 | 內存耗盡導致應用崩潰 |
系統資源搶占 | 系統優先回收高內存應用,增加被殺風險 |
Java內存核心機制:
-
GC工作原理:從GC Roots(如靜態變量、活動線程)出發,標記所有可達對象,回收不可達對象
3. 工作中常見內存泄漏場景及解決方案
場景1:單例持有Activity引用
錯誤代碼:
public class AppManager {private static AppManager instance;private Context context; // 危險!可能持有Activityprivate AppManager(Context context) {this.context = context; // 直接使用Activity Context}
}
問題:單例生命周期=應用生命周期,若持有Activity,會導致Activity無法回收。
解決方案:
this.context = context.getApplicationContext(); // 使用Application Context
場景2:非靜態內部類的靜態實例
錯誤代碼:
public class MainActivity extends AppCompatActivity {private static Resource resource; // 靜態變量void init() {resource = new Resource(); // 非靜態內部類實例}class Resource { // 非靜態內部類// 隱含持有外部類MainActivity的引用!}
}
問題:靜態變量resource生命周期=應用生命周期,其持有的Resource實例隱式持有Activity引用。
解決方案:
static class Resource { // 改為靜態內部類// 不再持有外部類引用
}
場景3:Handler內存泄漏
錯誤代碼:
public class MainActivity extends AppCompatActivity {private final Handler handler = new Handler() { // 匿名內部類@Overridepublic void handleMessage(Message msg) {// 隱式持有Activity引用}};
}
泄漏鏈:
MessageQueue → Message → Handler → Activity
解決方案:
// 1. 靜態內部類 + 弱引用
private static class SafeHandler extends Handler {private final WeakReference<Activity> weakActivity;SafeHandler(Activity activity) {weakActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {Activity activity = weakActivity.get();if (activity == null) return; // Activity已銷毀// 處理消息...}
}// 2. 在onDestroy中移除消息
@Override
protected void onDestroy() {super.onDestroy();handler.removeCallbacksAndMessages(null);
}
場景4:WebView內存泄漏
問題:WebView即使調用destroy()也可能泄漏(尤其Android 5.1以下)
解決方案:
// 方式1:動態創建+移除
FrameLayout container = findViewById(R.id.web_container);
webView = new WebView(getApplicationContext()); // 使用Application Context
container.addView(webView);@Override
protected void onDestroy() {container.removeView(webView);webView.destroy();super.onDestroy();
}// 方式2:獨立進程(在AndroidManifest.xml中配置)
<activity android:name=".WebActivity" android:process=":web"/>
// 退出時
finish();
System.exit(0);
場景5:資源未關閉
常見泄漏點:
// 忘記關閉導致泄漏
Cursor cursor = db.query(...);
InputStream is = getAssets().open(...);
BroadcastReceiver receiver = new MyReceiver();
解決方案:
@Override
protected void onDestroy() {super.onDestroy();cursor.close(); // 關閉數據庫游標is.close(); // 關閉文件流unregisterReceiver(receiver); // 注銷廣播
}
其他高頻泄漏點
場景 | 解決方案 |
---|---|
EventBus未反注冊 | EventBus.getDefault().unregister(this) |
集合對象未清理 | 定期清理無用引用?list.clear() |
動畫未取消 | animator.cancel() |
總結
"內存泄漏本質是無用對象無法被回收。工作中我重點關注五大場景:
1. 單例誤用:避免持有Activity,改用Application Context
2. 內部類泄漏:將非靜態內部類改為static,或使用弱引用
3. Handler泄漏:采用靜態Handler+弱引用,并在onDestroy移除消息
4. WebView泄漏:動態創建+及時移除,或使用獨立進程
5. 資源未關閉:在onDestroy中釋放Cursor/文件流/廣播等
通過LeakCanary檢測+代碼規范,可有效預防內存泄漏問題。"
檢測工具:
-
LeakCanary(自動檢測)
-
Android Profiler(手動分析)
補充
1. GC Roots對象有哪些?(可作為垃圾回收起點)
GC Root類型 | 代碼示例 |
---|---|
虛擬機棧中的引用 | void foo() { Object obj = new Object(); } (obj 是GC Root) |
方法區靜態屬性引用 | public static Object staticObj; |
方法區常量引用 | public static final Object CONSTANT = new Object(); |
本地方法棧JNI引用 | JNI調用的Native對象 |
同步鎖持有對象 | synchronized(lockObj) { ... } |
2. 內存區域存儲內容
區域 | 存儲內容 | 生命周期 |
---|---|---|
棧區 | 基本類型(int ,?boolean 等)、對象引用(地址) | 方法結束即釋放 |
堆區 | 對象實例(new 創建的對象)、數組 | 由GC管理回收 |
方法區 | 類信息、靜態變量(static )、常量池(final ) | 程序運行期間不釋放 |