由于Java的特有属性,其垃圾回收机制的垃圾回收的时间不确定性,造成了Android的内存泄露问题,本文主要是说明一些Android中的内存泄露问题
内存泄漏概念
-
在C/C++中,堆内存的开辟和销毁是通过程序员通过
malloc/free
和new/delete
去完成的,而在Java中,程序员只用开辟内存,而不用关心内存怎么去释放,这一切都交由Java的GC去完成 -
而内存泄漏问题,也就出在GC这里,如果一个对象已经不再被使用,这时候按理说应该回收其内存,但在这时候如果有其他使用的对象正在引用这个对象,那么GC就不能对其回收,从而继续保留在内存中,这样便产生了内存泄漏,究其本质,内存泄露的原因就是内存不在GC的掌控范围之内了
内存分区
内存可以人为的划分为几个区
- 静态区:存放静态数据和常量
- 方法区:存放代码
- 栈区:存放局部变量和基本数据类型,不会产生碎片,效率高,内存小
- 堆区:基本通过new关键字生成的对象都位于堆区,栈中只保留其索引位置
lrucache算法
按照使用频率决定回收顺序
在查阅ListView
源代码的时候,发现其开辟的内存空间在UI层不可见的时候并没有被回收,而是被复用了,也就是ConvertView
当需要加载大量数据或者图片的时候,往往是很耗费内存的,处理不当的话很容易直接造成OOM
那么,在内存和IO之间就需要一个平衡,也就是时间和空间的平衡性
引用分类
另外,为了更加利于GC回收,可以使用Java的一些特殊类进行标识:StrongReference
,SoftReference
,WeakReference
,PhatomReference
StrongReference
:强引用- 回收时机:从不回收
- 使用:对象的一般保存
- 生命周期:JVM停止的时候才会终止
SoftReference
:软引用- 回收时机:内存不足时
- 使用:
SoftReference<T>
结合ReferenceQueue
构造,有效期短 - 生命周期:内存不足为止
WeakRefence
:弱引用- 回收时机:垃圾回收时
- 使用:
SoftReference<T>
结合ReferenceQueue
构造,有效期短 - 生命周期:GC回收前
PhatomRefence
:虚引用- 回收时机:垃圾回收时
- 使用:和
ReferenceQueue
来跟踪对象呗垃圾回收期回收的活动 - 生命周期:GC回收前
一些占内存且生命周期长的对象,尽量使用软引用和弱引用
内存回收
垃圾清理的基本概念有
- 第一,找到未来无法存取的数据,例如所有不受指令操控的内存
- 第二,回收被利用过的资源
在GC运行的时候会占用内存,一样会降低系统性能,GC回收时间过长导致卡顿
单例模式的内存泄漏
常见的内存泄漏之一就是单例模式的使用,当活动销毁的时候,单例模式的内存并没有被销毁,此时就存在内存泄漏了
private static CtxUtil instance; private Context context; private CtxUtil(Context context){ this.context = context; } public static CtxUtil getInstance(Context context){ if(instance == null){ instance = new CtxUtil(context); } return instance; }
如果在调用的时候传递的是当前对象,那么很容易造成调用者的内存泄漏
CtxUtil ctxUtil = CtxUtil.getInstance(this);
但如果传递getApplicationContext()
,那么就可以解决这个问题,简单来说就是不要然类持有另一个类的实例就可以了
CtxUtil ctxUtil = CtxUtil.getInstance(getApplicationContext());
其原理就是当前对象和application的上下文生命周期并不相同
结论:能用Application
的context
就用Application
的,单例模式产生的Context
周期与app周期相同
Android Studio检查内存泄漏
在Android Studio
中有一个Monitor
工具,可以用来检测内存泄漏
主要涉及Memory
,CPU
,GPU
,Network
的监视
选择要监视的应用,查看其各项数据的使用情况,这里主要是Memory
的监视
- 选择
Initiate GC
手动释放内存,这时候应用会按照自己的需求再分配内存
如果对app进行操作,例如屏幕旋转,那么获得会销毁重新创建,此时如果Allocated
数值不断上升,那么就说明存在内存泄漏问题 - 选择
Dump Java Heap
,会生成堆内存快照,可以查看堆内存的使用情况
多个快照进行对比,可以得到哪些地方内存发生内存变化,也就可以分析出内存泄漏存在的地方
在Reference Tree
中可以查看引用情况
检查内存泄漏的一般方法
确定是否存在内存泄露
Android Monitors
的内存分析
最直观的看内存增长情况,知道该动作是否发生内存泄露
假如动作发生之前:GC完后内存1.4M; 动作发生之后:GC完后内存1.6M,那么就有理由怀疑发生了内存泄漏- 使用MAT内存分析工具
MAT分析heap
的总内存占用大小来初步判断是否存在泄露
Heap视图中有一个Type叫做data object
,即数据对象,也就是我们的程序中大量存在的类类型的对象
在data object
一行中有一列是Total Size
,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏
我们反复执行某一个操作并同时执行GC排除可以回收掉的内存,注意观察data object
的Total Size
值,正常情况下Total Size
值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况
反之如果代码中存在没有释放对象引用的情况,随着操作次数的增多Total Size
的值会越来越大
那么这里就已经初步判断这个操作导致了内存泄露的情况
先找怀疑对象(哪些对象属于泄露的)
MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)
快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象嫌疑犯)
技巧:Histogram
中还可以对对象进行Group
,比如选择Group By Package
更方便查看自己Package
中的对象信息
MAT分析hprof来定位内存泄露的原因所在(哪个对象持有了上面怀疑出来的发生泄露的对象)
Dump
出内存泄露当时
的内存镜像hprof
,分析怀疑泄露的类;- 把上面2得出的这些嫌疑犯一个一个排查个遍。步骤:
- 进入
Histogram
,过滤出某一个嫌疑对象类 - 然后分析持有此类对象引用的外部对象(在该类上面点击右键
List Objects--->with incoming references
) - 再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露(在类上面点击右键
Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references
) - 逐个分析每个对象的GC路径是否正常,此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了
- 进入
没有内存泄漏的标准
- 当app退出的时候,这个进程里面所有的对象应该就都被回收了,尤其是很容易被泄露的(
View
,Activity
)是否还内存当中,可以让app退出以后,查看系统该进程里面的所有的View
、Activity
对象是否为0,在退出以后多GC几次 - 工具:使用
AndroidStudio -> AndroidMonitor -> System Information -> Memory Usage
查看Objects
里面的views
和Activity
的数量是否为0 - 这样就可以知道
View
和Activity
是否发生了内存泄漏
来源:https://www.cnblogs.com/cj5785/p/10664643.html