一、 内存泄漏与内存溢出(OOM)
1. 内存泄露
- 垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。
- 内存泄露的危害: (1)过多的内存泄露最终会导致内存溢出(OOM)(2)内存泄露导致可用内存不足,会触发频繁GC,不管是Android2.2以前的单线程GC还是现在的CMS和G1,都有一部分的操作会导致用户线程停止(就是所谓的Stop the world),从而导致UI卡顿。
2. 内存溢出(OOM)
- Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。
- ActivityManager.getMemoryClass()可以查询当前APP的Heap Size阈值,单位是MB。
- 在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
- Android 2.x系统,当dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM,也就是说在2.x系统中,考虑native heap对每个进程的内存限制。
- Android 4.x系统,废除了native的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致),也就是说在4.x系统中,不考虑native heap对每个进程的内存限制,native heap只会收到本机总内存(包括RAM以及SWAP区或分页文件)的限制。
二、 LeakCanary内存检测工具配置
LeakCanary在GitHub上地址:https://github.com/square/leakcanary
在app的build.gradle中加入:
dependencies {
//leakcanary内存检测三方库
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
在项目的Application中加入(没有则创建):
@Override
public void onCreate() {
super.onCreate();
//初始化内存检测工具
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
三、 内存泄漏检测案例
非静态内部类引起的内存泄漏
导致内存泄漏的错误代码:
public class LeakActivity extends AppCompatActivity {
private static User mUser;
……
/**
* 非静态内部类引起的内存泄漏
*/
private void innerClassLeak() {
mUser = new User();
}
class User {
private String userName;
}
}
内存泄漏检测工具界面截图:

分析结果:
从下往可以看出导致内存泄漏是LeakActivity中private static User mUser;导致。导致该Activity销毁时释放不了.
解决后代码:
private static User mUser;
……
/**
* 非静态内部类引起的内存泄漏
*/
private void innerClassLeak() {
mUser = new User();
}
static class User {
private String userName;
}
}
将非静态类加入static修饰变为静态类,或者去除 private static User mUser; 中static修饰符
Thread引起的内存泄漏
导致内存泄漏的错误代码:
/**
* 线程引起内存泄漏
*/
private void threadLeak() {
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
SystemClock.sleep(10000);//睡眠10s,模拟耗时操作
}
}).start();
}
内存泄漏检测工具界面截图:

分析结果:
可以看出内存泄漏的原因是LeakActivity中有个任务导致,当次Activity销毁时,此线程任务未完成,导致此Activity无法释放。
解决后代码:
/**
* 线程引起内存泄漏
*/
private void threadLeak() {
new MyThread().start();
}
static class MyThread extends Thread{
@Override
public void run() {
//耗时操作
SystemClock.sleep(10000);//睡眠10s,模拟耗时操作
super.run();
}
}
自定义线程池工具类来开启此线程也可以。
Handler引起的内存泄漏
导致内存泄漏的错误代码:
Handler handler= new Handler();
/**
* Handler导致内存泄漏
*/
private void HandlerLeak() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
handler.postDelayed(this,1000);//每隔1000毫秒,循环发送消息
}
},1000);
}
内存泄漏检测工具界面截图:

分析结果:
从下到上可以看出LeakActivity中消息任务导致内存泄漏,此Activity关闭时消息任务还未关闭,导致此Activity未能销毁。
解决后代码:
Handler handler= new Handler();
/**
* Handler导致内存泄漏
*/
private void HandlerLeak() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
handler.postDelayed(this,1000);//每隔1000毫秒,循环发送消息
}
},1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);//remove正在执行的消息任务
}
当Activity销毁时调用关闭所有消息任务
单例Toast引起内存溢出
导致内存溢出的错误代码:
/**
* 静态Toast引起的内存泄漏
*/
private void ToastLeak() {
ToastUtils.showToast(this,new Date().getTime()+"");
}
private static Toast toast;
public static void showToast(Context context, String msg){
if(toast==null)
toast = Toast.makeText(context,"",Toast.LENGTH_SHORT);
toast.setText(msg);
toast.show();
}
内存泄漏检测工具界面截图:

分析结果:
从下至上看出LeakActivity 中Toast(这里使用了静态Toast) 的Context导致LeakActivity无法销毁,内存泄漏
解决后代码:
/**
* 静态Toast引起的内存泄漏
*/
private void ToastLeak() {
ToastUtils.showToast(this,new Date().getTime()+"");
}
private static Toast toast;
public static void showToast(Context context, String msg){
if(toast==null)
toast = Toast.makeText(context.getApplicationContext(),"",Toast.LENGTH_SHORT);
toast.setText(msg);
toast.show();
}
这里使用的是应用全局的Context。
什么是Context:https://possiblemobile.com/2013/06/context/
四、如何避免内存泄漏
- 使用轻量的数据结构:使用ArrayMap/SparseArray来代替HashMap,ArrayMap/SparseArray是专门为移动设备设计的高效的数据结构。
- 不要轻易使用Enum。
- Bitmap压缩处理:使用知名的第三方开源库处理。
- 不要使用String进行字符串拼接:频繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。
- 谨慎使用static对象:static对象的生命周期过长,应该谨慎使用。
- Context持有导致内存泄漏:Activity中的context被传递到其他实例中,这可能导致自身被引用而发生泄漏。