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 ) | 程序运行期间不释放 |