Android性能优化之内存优化

Android性能优化之内存优化

一、内存泄漏(Memory Leak)

对于Java来说new出来的Object在Heap堆上无法被GC回收(内存中存在无法回收的对象);内存泄漏时主要表现为内存抖动,可用内存慢慢变少。

1、内存泄漏检测工具-Memory Monitor(AS自带)

AndroidStudio自带的Memory Monitor可以方便观察堆内存的分配情况,可以大概观察是否存在Memory Leak(内存泄漏),并且可以粗略的观察有没有Memory Leak。

频繁的内存抖动,可能存在内存泄漏

  • A:initiate GC 手动触发GC操作;
  • B:Dump Java Heap 获取当前的堆栈信息,生成一个.hprof文件,AndroidStudip会自动使用HeapViewer打开;一般用于操作之后检测内存泄漏的情况;
  • C:Start Allocation Tracking 内存分配追踪工具,用于追踪一段时间的内存分配使用情况,能够知道执行一些列操作后,有哪些对象被分配空间。一般用于追踪某项操作之后的内存分配,调整相关的方法调用来优化app性能与内存使用;
  • D:剩余可用内存;
  • E:已经使用的内存。

点击Memory Monitor的Dump Java Heap,会生成一个.hprof文件,AndroidStudio会自动使用HeapViewer打开。

左面板说明:

  • Total Count 该类的实例个数
  • Heap Count 选定的Heap中实例的个数
  • Sizeof 每个实例占用的内存大小
  • Shallow Size 所有该类的实例占用的内存大小
  • Retained Size 该类的所有实例可支配的内存大小

右面板说明:

  • Instance 该类的所有实例对象(左侧Total Count为15,此处就有15个对象)
  • Depth 深度, GC Root点到该实例的最短链路数
  • Dominating Size 该实例可支配的内存大小

2、内存泄漏检测工具-MAT(Eclipse插件)

AS中的Memory-Monitor只是可以粗略的看出是不是有问题,而要知道问题出在哪里就需要借助MAT了。将生成的.hprof文件进行转换,然后使用MAT打开;

格式转换命令:hprof-conv 原文件路径 转换后文件路径

MAT打开.hprof

注意下面的Actions:

  • Histogram可以列出内存中每个对象的名字、数量以及大小。
  • Dominator Tree会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。
    一般使用最多的也是这两个功能。

Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存

  • 使用Histogram:
  1. 点击Histogram并在顶部的Regex中输入MainActivity会进行正则匹配,会将包含“MainActivity”的所有对象全部列出了出来,其中第一行就是MainActivity的实例。

  2. 对着想查看的对象点击右键 -> List objects -> with incoming references 查看具体MainActivity实例。

  3. 对想要查看的对象实例点击右键-> Path To Gc Roots -> exclude weak reference(排除掉软引用)。

注意:
this$0前面的图标的左下角有个圆圈,这代表这个引用可以被Gc Roots引用到,由于MainActivity$LeakClass能被GC Roots访问到导致其不能被回收,从而它所持有的其它引用也无法被回收了,包括MainActivity,也包括MainActivity中所包含的其它资源。
此时我们就找到了内存泄漏的原因。

  • 使用Dominator Tree

使用上面Histogram的操作方式也可以找到泄漏的具体原因,此处不再累述。
注意:每个对象前的图标的圆圈,并不代表一定是导致内存泄漏的原因,有些对象就是需要在内存中存活的,需要区别对待。

3、内存泄漏检测工具-LeakCanary

LeakCanary是square出品的一个检测内存泄漏的库,集成到App之后便无需关心,在发生内存泄漏之后会Toast、通知栏弹出等方式提示,可以指出泄漏的引用路径,而且可以抓取当前的堆栈信息供详细分析。

二、内存溢出(OOM)

除了避免内存泄漏之外,根据《Manage Your App’s Memory》,我们可以对内存的状态进行监听,在Activity中覆写此方法,根据不同的case进行不同的处理:

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
}
  • TRIM_MEMORY_RUNNING_MODERATE:你的应用正在运行并且不会被列为可杀死的。但是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。
  • TRIM_MEMORY_RUNNING_LOW:你的应用正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能。
  • TRIM_MEMORY_RUNNING_CRITICAL:你的应用仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个包含了一个运行态Service的进程。
    当应用进程退到后台正在被Cached的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:
  • TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的应用进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的应用的时候才能够迅速恢复。
  • TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的中部位置。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。
  • TRIM_MEMORY_COMPLETE: 系统正运行于低内存的状态并且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释放任何不影响你的应用恢复状态的资源。

三、内存抖动

产生内存抖动原因主要可能是以下两种:

  • 1、大量的对象瞬间频繁被创建,从而频繁触发GC,GC又会回收大量对象

  • 2、由于存在内存泄漏,导致很多对象等得不到释放从而长时间占用内存,此时再继续创建新的对象,内存很容易满了,很容易触发GC,每次又只能回收一点,所以不断地触发GC,GC次数变多,GC停顿总时间就变多了,也会导致我们常说卡顿情况。

常见的可能引发内存抖动的情形:

  • 循环中创建临时对象;
  • onDraw中创建Paint或Bitmap对象等;

例如之前使用过的有些下拉刷新控件的实现方式,在onDraw中创建Bitmap等多个临时大对象会导致内存抖动。

四、Bitmap内存管理

Bitmap的处理也是Android中的一个难点,当然使用第三方框架的话就屏蔽掉了这个难点。

  • Bitmap的内存模型
  • Bitmap的加载、压缩、缓存等策略
  • 版本的兼容等

五、内存优化的建议

1、合理地使用Service

内存管理最大的错误之一就是让Service一直运行。在后台使用service时,除非它需要被触发并执行一个任务,否则其他时候Service都应该是停止状态。另外需要注意Service工作完毕之后需要被停止,以免造成内存泄漏。

系统会倾向于保留有Service所在的进程,这使得进程的运行代价很高,因为系统没有办法把Service所占用的RAM空间腾出来让给其他组件,另外Service还不能被Paged out。 这减少了系统能够存放到LRU缓存当中的进程数量,它会影响应用之间的切换效率,甚至会导致系统内存使用不稳定,从而无法继续保持住所有目前正在运行的service。

建议使用JobScheduler,而尽量避免使用持久性的Service。还有建议使用IntentService,它会在处理完交代给它的任务之后尽快结束自己。

2、使用优化过的集合

比如Android API中提供内存优化的工具集合类,如SparseArray、ArrayMap等,使用这些API可以让我们的程序更加高效 这些会比Java中传统的HashMap工具相对低效,但是更省内存,但是属于那种时间换成空间手段。

3、避免内存抖动

垃圾回收通常不会影响应用的表现,但是短时间内多次的垃圾回收会消耗掉界面绘制的时间。系统花费在GC上的时间越多,进行界面绘制或流音频处理的时间就越短。 通常内存抖动会导致多次的GC,实践中内存抖动代表了一段时间内分配了临时对象。

例如:在For循环中分配了多个临时对象,或在onDraw()方法中创建了Paint、Bitmap对象,应用产生了大量的对象;这会很快耗尽young generation的可用内存,导致GC发生。

  • 避免在循环中创建临时对象

  • 避免在onDraw中创建Paint、Bitmap对象等。

4、使用onTrimMemory根据不同的内存状态做相应处理

5、谨慎使用多进程

6、尽可能避免所有的内存泄漏


   转载规则


《Android性能优化之内存优化》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Android性能优化之LeakCanary内存泄漏检测原理分析 Android性能优化之LeakCanary内存泄漏检测原理分析
LeakCanary内存泄漏检测原理分析一、LeakCanary原理介绍 LeakCanary如何检测内存泄漏的呢?主要分为7步: 1、RefWatcher.watch()创建了一个KeyedWeakReference用于去观察对象。
2020-01-01
下一篇 
Android性能优化之内存泄漏分析 Android性能优化之内存泄漏分析
Android性能优化之内存泄漏分析一、简述 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针。对于Java来说,就是new出来的Object放在堆Heap上无法被GC回收。 二、Java中得到内存分配 方法区(
2020-01-01