Android内存优化

作者: 小村医 | 来源:发表于2019-09-21 09:17 被阅读0次

一、什么是内存泄露?

当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而就导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏

内存分配的几种策略:

  1. 静态
    静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。
    它主要存放静态数据、全局的static数据和一些常量。

  2. 在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。
    栈内存包括分配的运算速度很快,因为内置在处理器的里面的。当然容量有限。

  3. 也叫做动态内存分配。有时候可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。

区别:

  • 堆是不连续的内存区域,堆空间比较灵活也特别大。
  • 栈式一块连续的内存区域,大小是有操作系统觉决定的。

我们所讨论内存泄露,主要讨论堆内存,他存放的就是引用指向的对象实体。

有时候确实会有一种情况:当需要的时候可以访问,当不需要的时候可以被回收也可以被暂时保存以备重复使用。
REcyclerView加载大量数据或者图片的时候,图片非常占用内存,一定要管理好内存,不然很容易内存溢出。滑出去的图片就回收,节省内存。
如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),
那么需要缓存---平衡好内存大小和IO,算法和一些特殊的java类。

  • 算法:lrucache(最近最少使用先回收)
  • 特殊的java类:StrongReference,SoftReference,WeakReference,PhatomReference

二、常用内存优化工具

Android内存优化工具:Memory Profiler

Android内存优化工具:MAT

1、确定是否存在内存泄露

  • Memory ProfilerMemory Profiler内存分析
    最直观的看内存增长情况,知道该动作是否发生内存泄露。
    动作发生之前: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的值会越来越大。
    那么这里就已经初步判断这个操作导致了内存泄露的情况。

2、 先找怀疑对象(哪些对象属于泄露的)

MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象。(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)
快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象嫌疑犯)
技巧:Histogram中还可以对对象进行Group,比如选择Group By Package更方便查看自己Package中的对象信息。

3. MAT分析hprof来定位内存泄露的原因所在

哪个对象持有了上面怀疑出来的发生泄露的对象:

  1. Dump出内存泄露“当时”的内存镜像hprof,分析怀疑泄露的类;
  2. 把上面2得出的这些嫌疑犯一个一个排查个遍。步骤:
    (1)进入Histogram,过滤出某一个嫌疑对象类
    (2)然后分析持有此类对象引用的外部对象(在该类上面点击右键List Objects--->with incoming references)
    (3)再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露 (在类上面点击右键Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    (4)逐个分析每个对象的GC路径是否正常,此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了!

罪魁祸首找到了,怎么解决应该不难了,不同情况解决办法不一样,要靠你的智慧了。

三、 常见内存泄漏

1、使用单例模式

2、非静态内部类的静态实例
非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,
就会长期维持着外部类的引用,组织被系统回收,解决办法就是使用静态内部类。

3、 多线程相关的匿名内部类
匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄露
导致外部类无法回收,直到耗时任务结束,解决办法就是在页面退出时结束线程中的任务。

Java中的Thread有一个特点就是他们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用 ,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill掉进程操作,所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉。

4.、Handler内存泄漏
Handler导致的内存泄露也可以被归纳为非静态内部类导致的,
handler内部message是被存储在MessageQueue中的,
有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,
如果handler是非静态的,就会导致他的外部类无法被回收,
解决办法就是:

  1. 使用静态handler,外部类引用使用弱引用处理。
  2. 在退出页面时移除消息队列中的消息。

5、Context导致内存泄露
根据场景使用Activity的Context还是Application的Context,
因为二者的生命周期不同,对于不必须使用Activity的Context的场景(dialog),
一律采用Application的Context,单例模式是最常见的发生此泄露的场景,
比如传入一个Activity的Context被静态类引用,导致无法回收。

6、资源未关闭导致
如Cursor,File等,内部往往都使用了缓存,会造成内存泄露,一定要确保关闭它并将引用置为nul

7、集合中的对象未清理
通常我们会添加一些对象的引用到集合中,当我们不需要用到该集合对象时,我们需要及时将该集合清空掉,如果不清空,将导致这个集合会越来越大。如果集合是静态的话,那情况将会更严重,因为声明为static的生命周期和整个app进程的生命周期一致。

8、监听器未关闭
很多需要register和unregister的系统服务要在合适的时候进行unregister,
手动添加的listener也需要及时移除。

9、使用Bitmap
bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,
避免静态变量持有大的bitmap对象

相关文章

网友评论

    本文标题:Android内存优化

    本文链接:https://www.haomeiwen.com/subject/niyjuctx.html