android程序应该怎么排查内存泄漏问题呢?
比如进入了一个新的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,但总体来说还是能起到一定的辅助作用。
总结:
内存泄漏常见原因:
1.集合类
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。 一开始的微信工程中使用的ButterKnife中的linkeahashmap就存在这个问题。
2.静态成员
Static成员作为gc root,如果一个对象被static声明,这个对象会一直存活直到程序进程停止。
3.单例模式
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被 JVM 正常回收,导致内存泄露。
这里如果传递Activity作为Context来获得单例对象,那么单例持有Activity的引用,导致Activity不能被释放。 不要直接对 Activity 进行直接引用作为成员变量,如果允许可以使用Application。如果不得不需要Activity作为Context,可以使用弱引用WeakReference,相同的,对于Service 等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。
4.未关闭/释放资源
BraodcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。 我们经常会写出下面的代码
当然这样写代码没问题,但是如果我们在close之前还有一些可能抛出异常的代码
那么现在这段代码存在隐患的。因为如果运行到fos2时候抛出了异常,那么fos也没办法close。 所以正确的方式应该是
因为如果write发生异常那么这个fos会因为没有close造成内存泄漏。
5.Handler
只要 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。
6.Thread 内存泄露
和handler一样,线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。 Thread和Handler都可以划分到为非静态包括匿名内部类的内存泄漏。
7.系统bug,比如InputMethodManager
|