比如进入了一个新的Activity,这时候我们的内存使用肯定比在前一个Activity大,而在界面finish返回后,如果内存没有回落,那么很有可能就是出现了内存泄漏。
从内存监控工具中观察内存曲线,是否存在不断上升的趋势且不会在程序返回时明显回落。那就很有可能出现内存泄漏的情况。
在Android Studio运行app,然后下面的点击Profiler后可以能看到
我们点击memory后
① 强制执行垃圾收集事件的按钮。
② 捕获堆转储的按钮。
③ 记录内存分配的按钮。 (新版AS 没有此按钮)
④ 放大时间线的按钮。
⑤ 跳转到实时内存数据的按钮。
⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
⑦ 内存使用时间表,其中包括以下内容:
我们进入APP一大堆页面并最终返回到主页。
然后强行gc ,再dump下内存查看。AS可以点击
然后等待一段时间会出现:
先按照包名来分组,
Allocaions : 对象数
Shallow Size : 对象占用内存大小
Retained Set : 对象引用组占用内存大小(包含了这个对象引用的其他对象)
当然一次dump可能并不能发现内存泄漏,可能每次我们dump的结果都不同,那么就需要多试几次,然后结合代码来排查。
Leaks 这一项如果不是0,证明存在内存泄漏。
定位是哪个界面以及哪个方法存在内存泄漏的方法如下:
对Android内存泄露 我们还可以使用著名的LeakCanary 来进行检测
https://github.com/square/leakcanary
这个库也有一些bug,但总体来说还是能起到一定的辅助作用。
内存泄漏常见原因:
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。
一开始的微信工程中使用的ButterKnife中的linkeahashmap就存在这个问题。
Static成员作为gc root,如果一个对象被static声明,这个对象会一直存活直到程序进程停止。
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被 JVM 正常回收,导致内存泄露。
这里如果传递Activity作为Context来获得单例对象,那么单例持有Activity的引用,导致Activity不能被释放。
不要直接对 Activity 进行直接引用作为成员变量,如果允许可以使用Application。如果不得不需要Activity作为Context,可以使用弱引用WeakReference,相同的,对于Service 等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。
BraodcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。
我们经常会写出下面的代码
当然这样写代码没问题,但是如果我们在close之前还有一些可能抛出异常的代码
那么现在这段代码存在隐患的。因为如果运行到fos2时候抛出了异常,那么fos也没办法close。
所以正确的方式应该是
因为如果write发生异常那么这个fos会因为没有close造成内存泄漏。
只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。特别是handler执行延迟任务。所以,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,
使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
和handler一样,线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。
Thread和Handler都可以划分到为非静态包括匿名内部类的内存泄漏。