1.什么是内存泄露

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃(内存溢出OOM)等严重后果。
内存泄露的危害:

  • 用户对单次的内存泄漏并没有什么感知,但是当泄漏积累到内存都被消耗完,就会导致卡顿,甚至崩溃;
  • gc回收频繁 造成应用卡顿ANR:
  • 当内存不足的时候,gc会主动回收没用的内存.但是,内存回收也是需要时间的.
  • 内存回收和gc回收垃圾资源之间高频率交替的执行.就会产生内存抖动.
  • 很多数据就会污染内存堆,马上就会有许多GCs启动,由于这一额外的内存压力,也会产生突然增加的运算造成卡顿现象,
  • 任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行,所以垃圾回收运行的次数越少,对性能的影响就越少;

2.内存泄漏的原因

Android虚拟机有垃圾回收机制,会回收掉大部分的内存空间,但是有一些逻辑上已经不再使用的对象,被GC Root直接或间接引用,导致垃圾回收器不能回收它们。

2.1.虚拟机内存模型

Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆。 堆是运行时数据区域,所有类实例和数组的内存均从此处分配。 非堆是JVM留给自己用的,包含方法区、虚拟机栈、本地方法栈、程序计数器。

对于绝大多数应用来说,堆内存是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。堆内存由垃圾回收器的自动内存管理系统回收,出现内存泄漏也主要在这个区域

2.2.可回收对象的判定

可达性分析算法:从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。 即使循环引用了,只要没有被GC Roots引用了依然会被回收。 但是,这个GC Roots的定义就要考究了,Java语言定义了如下GC Roots对象:

虚拟机栈(帧栈中的本地变量表)中引用的对象。 方法区中静态属性引用的对象。 方法区中常量引用的对象。 本地方法栈中JNI引用的对象。

3.内存泄漏检测工具

3.1.LeakCanary

通过集成 LeakCanary ,一旦检测到内存泄漏,LeakCanary 就会 dump Memory 信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,极大地方便了Android应用程序的开发。

3.1.1.原理

LeakCanary 是通过在 Application 的 registerActivityLifecycleCallbacks 方法实现对 Activity 销毁监听的,在 Activity 在销毁时,将 Activity 包装到 WeakReference 中,被 WeakReference 包装过的 Activity 对象如果能够被回收,则说明引用可达,垃圾回收器就会将该 WeakReference 引用存放到 ReferenceQueue 中。相反就可能发生了内存泄漏。

3.1.2.接入

dependencies {// debugImplementation because LeakCanary should only run in debug builds.debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

通过过滤 Logcat 中的 LeakCanary 标签来确认 LeakCanary 在启动时正在运行:

D/LeakCanary: LeakCanary is running and ready to detect memory leaks.

在 LeakCanary2.0 之前我们接入的时候需要在 Application.onCreate 方法中显式调用 LeakCanary.install(this); 开启 LeakCanary 的内存监控。
从 LeakCanary2.0 开始通过库里注册的 ContentProvier 自己开启 LeakCanary 的内存监控,无需用户手动再添加初始化代码。

3.1.3使用

安装测试应用后,手机上会出现如下两个图标:左侧的是测试应用的图标,右侧是自动安装的 LeakCanary 的图标。
打开可能存在内存泄漏页面,返回退出页面。等待大概10秒,LeakCanary 就会检测到,并进行分析。在通知栏可以看到进度。

分析完成后,然后在通知栏会收到通知。

点开通知,会打开 LeakCanary 的界面


另外,在 Logcat 中也可以看到 LeakCanary 的相关信息。LeakCanary 将应用中发现的泄漏分为两类:应用程序泄漏和库泄漏。

====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
......
====================================
0 LIBRARY LEAK
————————————————

3.2.Profiler

android studio自带的Profiler MEMORY工具,通过Capture heap dump抓取堆内存快照分析内存泄漏。

如存在泄漏,会提示相应泄漏对象:

根据泄漏对象references查看对象在哪里被引用,一般看是否被长生命周期对象(单例、静态对象、handler等)引用导致无法回收

4.常见内存泄漏场景

4.1.单列、静态变量导致内存泄漏

单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
常见的有:

  • Context:单例持有Activity、Service的Context。正确的做法是在单列、静态变量中如果需要用到Context可以用Application的Context替换
    反例:
public class UpdateManager {private static volatile UpdateManager mInstance;private Context mContext;private UpdateManager(Context context) {mContext = context;}public static UpdateManager getInstance(Context context) {if (mInstance == null) {synchronized (UpdateManager.class) {if (mInstance == null) {mInstance = new UpdateManager(context);}}}return mInstance;}
}public class StaticClass {// 定义1个静态变量private Static Context mContext;//...// 引用的是Activity或Service的contextmContext = context; // 当Activity或Service需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露
}

正例:

public class UpdateManager {//...省略代码private UpdateManager(Context context) {mContext = context.getApplicationContext();}//...省略代码
}
  • 监听回调:单例中只提供了添加监听的方法,无法取消监听。正确的做法是添加监听后在适当的位置取消监听
    反例:
public class UpdateManager {public void addUpdateListener(UpdateListener listener) {if (mUpdateListener.contains(listener)) {return;}mUpdateListener.add(listener);}
}

正例:

public class UpdateManager {public void addUpdateListener(UpdateListener listener) {if (mUpdateListener.contains(listener)) {return;}mUpdateListener.add(listener);}public void removeUpdateListener(UpdateListener listener) {mUpdateListener.remove(listener);}
}

4.2.非静态内部类、匿名类导致内存泄漏

非静态类和匿名类默认会持有外部类的引用,所以在长生命周期的类中引用了非静态内部类、匿名类就会导致内存泄漏。常见的解决方式是静态内部类+WeakReference

  • Handler:使用非静态内部类或匿名类定义Handler变量,由于使用的getMainLooper,那发送的Message回被添加在主线程的Looper中,其生命周期 = 应用的生命周期,Message又持有mHandler,mHandler是非静态内部类又持有外部类TestActivity,从而导致TestActivity无法回收
    反例:
public class TestActivity extends Activity {private Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == 1) {showContent();}}};//使用mHandler.sendEmptyMessageDelayed(5000,1)发送消息,并未清理
}

正例一:在适当的位置情况已发送的Message

@Override
protected void onDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);
}

正例二:使用静态内部类+WeakReference

private MyHandler mHandler;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mHandler = new MyHandler(this);
}private static class MyHandler extends Handler {private final WeakReference<TestActivity> mWeakActivity;public MyHandler(TestActivity testActivity) {super();mWeakActivity = new WeakReference<>(testActivity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);TestActivity testActivity = mWeakActivity.get();if (testActivity == null) {return;}if (msg.what == 1) {testActivity.showContent();}}
}
  • 多线程:AsyncTask、实现 Runnable 接口、继承 Thread 类,当工作线程正在处理任务时,如果外部类销毁, 由于工作线程实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。
    反例:
public class TestActivity extends Activity {private void execute() {new Thread() {@Overridepublic void run() {doSomething();}}.start();}private void doSomething(){//Do time-consuming operations}
}

正例:同样可以使用静态内部类+WeakReference处理,但一般不直接new Thread进行异步操作,而是使用线程池,最好是用RxJava、kotlin协程,这样可以更好管理线程,并且可以在组件销毁时停止异步操作

public class TestActivity extends Activity {private static class MyThread extends Thread {private final WeakReference<TestActivity> mWeakActivity;public MyThread(TestActivity activity) {mWeakActivity = new WeakReference<>(activity);}@Overridepublic void run() {super.run();if (mWeakActivity.get() == null) {return;}doSomething();}private void execute() {new MyThread(this).start();}
}

4.3.资源对象使用后未关闭

反例:未释放资源

public static void writeFile(File file, String content) {try {BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));bw.write(content);bw.flush();} catch (IOException e) {e.printStackTrace();}
}

正例:在finally中close

public static void writeFile(File file, String content) {BufferedWriter bw = null;try {bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));bw.write(content);bw.flush();} catch (IOException e) {e.printStackTrace();} finally {if (bw != null) {try {bw.close();} catch (IOException e) {e.printStackTrace();}}}
}

4.4.系统服务

Android中有很多服务,比如PowerManager,AlarmManager,NotificationManager等,通常使用起来也很方便,就是使用Context.getSystemService方法来获得。但部分Manager会持有调用的Context,例如:PowerManager

public PowerManager(Context context, IPowerManager service, IThermalService thermalService,Handler handler) {mContext = context;mService = service;mThermalService = thermalService;mHandler = handler;
}

所以如果获取PowerManager的时候使用的是Activity或Service的Context,并且在单例或static类中保存了PowerManager,就会导致内存泄漏。泄漏分析过程可参考:https://cloud.tencent.com/developer/article/1328418
解决方案:

  1. 不使用静态持有Manager,就算Manager中持有Activity或Service的Context,只要生命周期与Activity或Service保存一致,也不会出现内存泄漏
  2. 使用Application Context获取系统服务。但注意和UI相关的服务在Activity或ContextThemeWrapper中会做了优化,如果全部都使用Application Context将会得不偿失,所有需要试情况而定
//ContextThemeWrapper也优先处理了LayoutManager服务
@Override
public Object getSystemService(String name) {if (LAYOUT_INFLATER_SERVICE.equals(name)) {if (mInflater == null) {mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);}return mInflater;}return getBaseContext().getSystemService(name);
}

a. 如果服务和UI相关,则用Activity
b. 如果是类似ALARM_SERVICE,CONNECTIVITY_SERVICE建议有限选用Application Context
c. 如果出现出现了内存泄漏,排除问题,可以考虑使用Application Context

4.5.广播未注销

无论是系统广播还是本地广播,注册与反注册需成对出现

Android常见内存泄漏相关推荐

  1. Android 常见内存泄漏及解决方法

    Android中的内存泄漏: 先说一下为什么会出现内存泄漏: Android程序开发中,如果一个对象已经不需要被使用了,本该被回收时,而这时另一个对象还在持有对该对象的引用,这样就会导致无法被GC回收 ...

  2. 来点干货 | Android 常见内存泄漏与优化(二)

    作者 | 无名之辈FTER 责编 | 夕颜 出品 | CSDN(ID:CSDNnews) 在昨天的Android 内存泄漏问题多多,怎么优化?一文中,我们详细阐述了Java虚拟机工作原理和Androi ...

  3. Android常见内存泄漏及优化总结

    前言 最近在整理回顾零碎知识点,今天整理下Android内存优化方案分享给大家. 在Android开发中,一些不好的编程习惯会导致我们的开发的app存在内存泄露的情况.下面简单介绍一些在Android ...

  4. Android常见内存泄漏以及解决办法

    #1. 注意你的Context引用 ##尝试使用合适的context## (1).Toast能在许多的Activity看到, 使用 getApplicationContext() (2).servic ...

  5. Android性能优化(2):常见内存泄漏与优化(二)

    文章目录 1. Android虚拟机:Dalvik和ART 1.1 JVM与Dalvik区别 1.2 Dalvik与ART区别 1.3 Dalvik/ART的启动流程 2. 常见内存分析工具 2.1 ...

  6. Android 中内存泄漏的原因和解决方案

    之前研究过一段时间关于 Android 内存泄漏的知识,大致了解了导致内存泄漏的一些原因,但是没有深入去探究,很多细节也理解的不够透彻,基本上处于一种似懂非懂的状态,最近又研究了一波,发现有很多新的收 ...

  7. android分析内存工具,Android Studio内存泄漏分析工具汇总

    Android Studio内存泄漏分析工具汇总 时间:2017-04-25     来源:Android开发学习网 在Android开发过程中,让人头疼的就是内存泄露问题了,很小的一个错误都会引起内 ...

  8. Android Native内存泄漏诊断

    Android Native内存泄漏诊断 1.基础诊断方法 特点:操作简单,但只能判断是否有泄漏,但需使用者自行判断泄漏在哪里 命令行方式 adb shell dumpsys meminfo vStu ...

  9. Android之内存泄漏以及解决办法(持更)

    Android之内存泄漏以及解决办法 文章链接:http://blog.csdn.net/qq_16628781/article/details/67761590 知识点: 单例造成的内存泄漏原因和解 ...

最新文章

  1. 大学毕业后,我将何去何从?
  2. CSS布局代码:两列布局实例
  3. mysql数据库建站教程视频_Mysql数据库零基础到精通视频教程(共6天)
  4. tq2440 jlink连接问题
  5. 有了这个Java项目经历,面大厂稳了!
  6. synamic-datasource-spring-boot-starter实现动态数据源Mysql和Sqlserver
  7. 关键词提取算法—TF/IDF算法
  8. 开发Teams Tabs应用程序
  9. Resource is out of sync with the file system解决办法
  10. aix系统服务器日志外发,AIX系统日志
  11. 基于linux环境tcp网络编程(在线英英词典)文档【3】
  12. double转换成百分数
  13. 黑马前端基础-HTML-SE
  14. HORAE深入思考及长久性论证
  15. 二进制调色器程序管理
  16. 从业务开始:一招攻破数据分析思路大难题
  17. O2O优惠券核销-数据分析
  18. 极路由 刷linux,极路由 刷uboot + openwrt , 以及连接校园网(netkeeper)
  19. 关于Service抛出异常事务不回滚的原因和解决方法
  20. 小A是大四的学生,还有半年就要毕业了

热门文章

  1. excel导出多重表头utils_java excel复杂表头和复杂数据导出
  2. 除了GitHub,国内开发者常用的代码托管工具盘点
  3. CSS 文本溢出 text-overflow属性
  4. QT4.8的只能确认不能拒绝的告白小应用
  5. 就这样的,她,离开了我……
  6. 计算机设计计划进度安排怎么写,软件产品开发进度计划表.docx
  7. mysql parquet_Spark与Apache Parquet
  8. 基于轨迹数据的人口流向分析技术,精准病毒传播追踪
  9. 万字总结阿里大数据之路-数据技术篇(建议收藏)
  10. React兄弟组件间传参