最近做了内存泄漏的总结,这里先把PPT搬上来,有人看再做优化。
什么是内存泄漏?
内存泄漏,就是指程序申请使用的内存没有及时释放。
Android应用的内存泄漏主要在虚拟机层,也有Native层的。
有的内存泄漏可能导致程序占用的内存增高,直至OOM;有的内存泄漏比较隐蔽,也可能造成严重后果。比如binder通信泄漏,会导致TransactionTooLargeException。同时,内存占用偏高时会引发频繁GC,导致应用卡顿。
APP内存回收算法
标注并清理法 GC会从内存遍历的根节点(GCRoots,比如threadstack中的变量,JNI中的全局变量,zygote中的classloader等)开始,对heap遍历一次。保留所有被GCRoots直接或间接引用的对象,剩下的对象作为垃圾回收。
标注并清理法克服了引用计数法中的循环引用问题,对程序几乎没有额外性能开销;缺点是垃圾回收时会暂停进程内其他线程,容易产生内存碎片。
Davik和ART的内存回收
主流的大部分Davik采取的都是标注与清理(Mark and Sweep)回收算法,也有实现了拷贝GC的, java堆实际上是由一个Active堆和一个Zygote堆组成的。
ART也使用了标注并清理算法,Java堆包括Image Space、Zygote Space、AllocationSpace和LargeObject Space四个,分别采用不同的回收策略,提高了GC效率,减少pause时间,而且还在内存分配上对大内存的有单独的分配区域,同时还能有算法在后台做内存整理,减少内存碎片。GC的效率提高了2-3倍。
第一招
看内存占用:AndroidStudio\Android monitor\Memory+allocator
反复操作功能模块,看内存占用
正常情况下内存占用都会稳定在一个有限的范围内,也就是说由于程序中的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;如果有缓存,也会在初次操作内存升高后维持在一个稳定水平,因为后面的操作会从缓存中读取,而非新建对象。
反之,如果代码中存在没有释放对象引用的情况,则内存在每次GC后不会有明显的回落,随着操作次数的增多,内存占用会越来越大,直到到达一个上限后导致进程OOM被kill掉。
看GC的回收信息
LogCat中的GC信息
【GC回收原因】【释放了多少内存】【内存信息】【gc中断时间】【gc总耗时】
D/dalvikvm: GC_FOR_ALLOCfreed 447K, 20% free 10501K/12984K,
paused 15ms, total 15ms
在堆上分配对象时,内存不足触发的GC
D/dalvikvm: GC_EXPLICITfreed 155K, 19% free 10592K/12984K,
paused2ms+3ms, total 27ms
应用程序强制GC,如调用System.gc,线程被杀死或Binder通信中断
GC_CONCURRENTfreed 506K, 26% free 10219K/13676K,
paused1ms+1ms, total 12ms
当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发
GC操作来释放内存,异步进行,但也会暂停工作线程
D/dalvikvm: GC_BEFORE_OOMfreed 9K, 6% free 54334K/57576K,
paused 342ms, total 342ms
OOM之前,执行最后一次回收
ART中的GC类型
kGcCauseForAlloc,当要分配内存的时候发现内存不够的情况下引起的GC,这种情况下的GC会stopworld
kGcCauseBackground,当内存达到一定的阀值的时候会去出发GC,这个时候是一个后台gc,不会引起stopworld
kGcCauseExplicit,显示调用的时候进行的gc,如果art打开了这个选项的情况下,在system.gc的时候会进行gc
第二招
弱引用跟踪: LeakCanary
Java的几种引用关系
强引用GC不会回收该对象
软引用 SoftReference系统内存不足时释放
弱引用 WeakReference,GC会回收
虚引用 PhantomReference,GC会回收,get方法返回结果始终为null,
必须用ReferenceQueue
代码中配置
LeakCanary默认对Activity进行了监控,Fragment和其他对象需要
手动监控。
public class CommentListContentFragment extends Fragment {
@Override
public void onStop() {
super.onStop();
MyApplication.getInstance().getWatcher().watch(mAdapter);
}
@Override
public void onDestroy() {
super.onDestroy();
MyApplication.getInstance().getWatcher().watch(this);
}
}
看log信息:
D/LeakCanary: In com.jingdong.app.mall:5.0.0:27074.
D/LeakCanary: * com.jingdong.app.mall...ProductListActivity has leaked:
D/LeakCanary: * GC ROOT static com.jingdong.app.mall...SearchEngine.engine
D/LeakCanary: * references com.jingdong.app.mall...SearchEngine.httpGroupUtil
D/LeakCanary: * references com.jingdong.cleanmvp...HttpGroupUtil.httpGroup
D/LeakCanary: * references com.jingdong.common...HttpGroupAdapter.httpGroupSetting
D/LeakCanary: * references com.jingdong.common..HttpGroup$HttpGroupSetting.myActivity
D/LeakCanary: * leaks com.jingdong.app.mall...ProductListActivity instance
D/LeakCanary: * Reference Key: f990321d-a4d4-4bb7-b0de-238e68f3e1ce
D/LeakCanary: * Device: samsung samsung GT-N7100 t03gzc
D/LeakCanary: * Android Version: 4.3 API: 18 LeakCanary: 1.3.1
D/LeakCanary: * Durations: watch=515ms, gc=54ms, heap dump=136ms, analysis=4560ms
也可以分析dump信息
Dump存放目录:sdcard/download/leakcanary/detected_leaks
第三招 分析内存快照:dump MAT
反复操作后,手动gc,然后利用Android Studio获取内存快照,用hprof-conv工具转换
Shallow size
对象本身占用的内存大小。一个普通对象的shallowsize,取决于对象的成员变量(field)的类型和个数。数组的shallowsize,取决于成员类型和数组长度。集合的shallowsize就是集合中所有对象的shallowsize之和。
Retained size
指对象自身的shallow size和其支配的对象的shallowsize之和。也就是说,GC回收这个对象能释放的内存大小。
可以使用OQL语言,查询某个对象。点击!感叹号执行。
也可以截取反复操作前,和反复操作后的dump,同时导入mat中对比,再看那些增加的对象及其引用。
常见android app内存泄漏
1、非静态内部类的静态实例容易造成内存泄漏
2、activity使用静态成员
3、使用handler时的内存问题
4、注册某个对象后未反注册
5、集合中对象没清理造成的内存泄露
6、资源对象没关闭造成的内存泄露
7、线程未终止造成的内存泄露
8、Bitmap使用不当,没有及时回收
9、构造Adapter时,没有使用缓存的convertView
10、在经常调用的方法中创建对象,如循环中创建对象