腾讯大神教你如何解决android内存泄露
经过长期迭代、app出现了占用内存过高的问题,上半年抽时间做了次内存泄露的分析和处理,把app占用内存从70多M降低到30多M,这里做下总结。
修复前
修复后
分析
这里主要是针对业务实际代码进行一下分析和处理,下面会总结实际使用到的一些知识。
1. java中的内存分配
1.静态存储区:编译时就分配好,在程序整个运行期间都存在,它主要用于存放静态数据和常量;
2.栈区:当方法执行时,会在栈区内存中创建方法内部的局部变量,方法结束后自动释放内存;
3.堆区:通常存放new出来的对象。由java垃圾会收器进行回收。内存泄露一般是指堆内存的泄露。
2.如何识别对象无引用?
1.引用计数法:直接计数,简单高效,Python便是采用该方法。但是如果出现两个对象互相引用,即时它们都无法被外界访问到,计数器不为0,它们也得不到回收。
为了解决这种问题,java采用的是第二种方法。
2.可达性分析法,这个方法设置了一系列的“GC Roots”对象作为索引七点,如果一个对象与起点对象之间均无可达路径,那么这个不可达的对象就会成为回收对象。
这种方法处理两个对象相互引用的问题,如果两个对象均没有外部引用,那么就会被判断为不可达对象进而被回收。
这边可以被当做GC roots的对象主要有以下:
android给出的解析
参考https://developer.android.com/studio/profile/am-memory.html?utm_source=android-studio
java给出的解析
参考http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3
Garbage Collection Roots
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:
1.System Class
Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
2.JNI Local
Local variable in native code, such as user defined JNI code or JVM internal code.
3.JNI Global
Global variable in native code, such as user defined JNI code or JVM internal code.
4.Thread Block
Object referred to from a currently active thread block.
5.Thread
A started, but not stopped, thread.
6.Busy Monitor
Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
7.Java Local
Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
8.Native Stack
In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
9.Finalizable
An object which is in a queue awaiting its finalizer to be run.
9.Unfinalized
An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
9.Unreachable
An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
10.Java Stack Frame
A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
11.Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
总结下:当前运行线程持有的对象;已经开启但是没有终止运行的线程;方法运行时的引用参数(被栈引用的堆对象);native方法中引用的对象;静态变量和方法;bootstrap loader加载的class对象;
3.内存泄漏
对于保持着引用,但是逻辑上不会再用到的对象,垃圾回收器是不会进行回收的。这些对象会累积在内存中,直到程序结束。这就是所谓的内存泄露。
传统手工方法:
反复操作应用,观测内存变化,gc后内存是否有回落到原先的值;比较粗略,难发现问题。
通过代码检测:
在程序中启动长期工作的后台线程LeakThread进行内存检测
向application注册页面生命周期的监听
在监听类中对 onActivityDestoryed(Activity activity) 的事件回调做处理:在activity被调用onDestroy的时候,创建该activity的weak reference对象ref
LeakThread每隔段时间就去检测该ref.get()是否为空,如果为空则说明正常释放,不为空就手动触发gc,再隔断时间去查看是否已被回收。如果没有就dump heap,进而分析并计算到gc roots的最短路径,判断是否有泄露,如果有,就通知出来。
已经有实现的开源检测工具LeakCanary:https://github.com/square/leakcanary 强烈推荐!目前我们项目已经集成并使用了。
参考:http://km.oa.com/group/15137/articles/show/295872?kmref=search&from_page=2&no=7
1.如何导出.prof文件?
如何取得内存快照,生成prof文件
2.如何分析.prof文件?
studio自带的内存泄露分析工具
导出该prof文件,生成MAT可分析的格式。
使用指令:hprof-conv input.hprof output.hprof
ABLEOU-MC4:~ ableou$ cd /Users/ableou/Downloads/adt-bundle-mac-x86_64-20140321\ 2/sdk/tools/
ABLEOU-MC4:tools ableou$ hprof-conv /Users/ableou/work/AS_ZhongCai500/captures/com.zhongcai500_2017.06.29_15.24.hprof /Users/ableou/work/AS_ZhongCai500/captures/com.zhongcai500_out_2.hprof
接下来,对转换后的prof文件,使用MTA分析
3.使用MAT进行分析
参考网站:http://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT.html
1.CONTEXT_LEAK
持有activity对象不释放
让在整个程序运行过程中不会消亡的对象(如 单例对象)持有activity,可以参看initActivity之前的代码
非静态内部类持有外部类对象,并做耗时操作
延时处理的handler中持有的对象
解决:
1.单例对象尽量使用applicationContext
2.如果必须在单例对象中使用activity,则定义接口,让activity实现该接口,并通过接口使用,在activity destroy的时候需要主动触发回收。
3.修改为持有弱引用
4.在activity onDestroy之前把不需要处理的回调进行关闭。
2.RESOURCE_LEAK
我们代码主要存在:输入输出流没关闭
还有cursor没关闭、注册监听没有反注册等等。
如果想看更多可以参考:
http://km.oa.com/group/20528/articles/show/186773?kmref=search&from_page=1&no=10
https://zhuanlan.zhihu.com/p/25213586
小结:知道了泄露的原理和发现泄露的方法之后,解决泄露就不成为一个问题了。
END