内存泄漏介绍
1.什么是OOM?
OOM(out of memory)即内存溢出.在程序中,对内存使用超过一定的阀值就会导致内存溢出. 而当一个对象已经不需要在使用了,本该被回收,而另一个正在使用的对象持有它的引用,导致该对象不能被回收就产生了内存泄漏.内存泄露太多导致无法继续申请内存是导致OOM的主要原因之一.
2.OOM导致的现象?
1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
2.由于APP运行内存限制,会导致直接崩溃(OutOfMemoryError)
3.触发Low Memory Killer机制,应用莫名被杀
3.什么是内存抖动
内存泄漏发生时的主要表现为内存抖动,可用内存慢慢变少,在Android studio中可以通过Android Profiler工具查看内存抖动情况
堆内存都有一定的大小,能容纳的数据是有限制的,当Java堆的大小太大时,垃圾收集会启动停止堆中不再应用的对象,来释放内存。当在极短时间内分配给对象和回收对象的过程就是内存抖动。
内存抖动一般是在循环语句中创建临时对象或在绘制时配置大量对象导致。 内存抖动会带来UI的卡顿,因为大量的对象创建,会很快消耗剩余内存,导致GC回收,GC会占用大量的帧绘制时间,从而导致UI卡顿
导致内存泄漏(溢出)的原因有哪些?
1. 创建的资源没有及时释放:
如何避免:
资源性对象及时关闭,使用的任何资源都要及时关闭或者异常处理,保证在最恶劣的情况下资源可以得到释放 (如:Cursor、File、Receiver、Sensor)
资源的注册和反注册成对出现(广播,观察者) 如事件注册后未注销,会导致观察者列表中持有Context对象的引用
页面退出时及时清理一些资源占用(集合对象,WebView) 容器中的对象在不用的时候及时清理,WebView存在着内存泄漏的问题,在应用中只要使用一次,WebView,内存就不会被释放掉.
资源重复利用(使用adapter的时候使用convertView)
2. 保存了耗用内存过大的对象(Bitmap)
如何避免:
Bitmap没有使用的时候及时recycle释放内存
对大图进行压缩,使用软引用或弱引用(使用这两种引用代码需要做不为空判断)
使用缓存技术(LruCache和DiskLruCache)
3.Static引用的资源 消耗过多的实例(Context的使用)
如何避免:
尽量避免static成员变量引用资源 消耗过多的实例,比如Context;静态变量不要持有大数据对象
使用软引用代替强引用**
尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题
对于内部类尽量使用静态内部类,避免由于内部类导致的内存泄漏(静态内部类可以通过软引用使用外部的Context)如:Handler使用静态内部类
4.线程生命周期不可控导致内存泄漏
如何避免:
将线程的内部类,改为静态内部类,在线程内部采用弱引用保存Context引用
线程优化:避免程序中存在大量的Thread.可以使用线程池,并且页面退出时,终止线程.**
例:
在activity中handler发一个延时任务,activity退出后,延迟任务的message还在主线程,它持有activity的handler引用,所以造成内存泄漏(handler非静态类,它会持有外部类的引用,也就是activity);
这里可以把handler声明为static的,则handler的存活和activity生命周期无关了,如果handler内部使用外部类的非static对象(如:Context),应该通过弱引用传入,activity销毁时,移除looper线程中的消息.
常见的内存泄漏案例分析
1、单例模式引起的内存泄露
由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏
内存泄漏代码片段:
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context;
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private View mView = null;
public void setXXView(View xxView) {
mView = xxView;
}
}
解决方案:
- 传入的Context使用ApplicationContext;
- 将该属性的引用方式改为弱引用;
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context.getApplicationContext();
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private WeakReference mView = null;
public void setXXView(View xxView) {
mView = new WeakReference(xxView);
}
}
2.Handler引发的内存泄漏
内存泄漏代码片段:
当Activity退出时,延时任务Message还在主线程的MessageQueue中等待,此时的Message持有Handler的强引用,并且Handler是Activity类的非静态内部类,所以也默认持有Activity的强引用.
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
解决方案: 1.使用静态内部类,通过弱引用传入外部类的Context 2.在onDestroy中调用mHandler.removeCallbacksAndMessages(null)
private final Handler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
static class MyHandler extends Handler {
private SoftReference reference;
public MyHandler(Activity activity) {
// 持有 Activity 的软引用
reference = new SoftReference(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 处理消息
}
}
}
}
4.内部类引起的内存泄漏
内部类默认持有外部类强引用,容易出现内存泄漏
内存泄漏代码片段:
public void createNonStaticInnerClass(){
CustomThread mCustomThread = new CustomThread();
mCustomThread.start();
}
public class CustomThread extends Thread{
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(5000);
Log.i(TAG,"CustomThread ------- 打印");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
解决方案: 1.把线程类声明为静态的类,如果要用到Activity对象,那么就作为参数传入且为WeakReference 2.在Activity的onDestroy时,停止线程的执行
public static class CustomThread extends Thread{
private WeakReference mActivity;
public CustomThread(MainActivity activity){
mActivity = new WeakReference(activity)
}
}
5.Activity Context 的不正确使用引起的内存泄漏
使用ApplicationContext代替Activity Context ,因为ApplicationContext的生命周期也就是该应用生命周期,不依赖于activity的生命周期
内存泄露的工具?
1. MAT工具(很全面,但入手较难,MAT为Eclipse自带工具)
2. Android Profiler(图像化工具,AndroidStudio自带工具)
3. LeakCanary工具(简便)
源码:https://github.com/square/leakcanary
支持Eclipse的库:http://download.csdn.net/detail/ytuglt/9533490
相关链接直达:
Android APP性能优化之 ---- 布局优化(一)
Android APP性能优化之 ---- 内存优化(二)
Android APP性能优化之 ---- 代码优化(三)
Android APP性能优化之 ---- 优化监测工具(四)
Android APP性能优化之 ---- APK瘦身 App启动优化
Android内存泄露OOM的原因及解决方法