前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android性能优化系列---管理你的app内存(一)

Android性能优化系列---管理你的app内存(一)

作者头像
Criss@陈磊
发布2019-08-02 11:06:26
1.1K0
发布2019-08-02 11:06:26
举报
文章被收录于专栏:测试技术圈测试技术圈

Random-access memory(RAM)在任何软件开发环境都是稀有资源,在移动操作系统物理内存有限的情况下将显得更加珍贵.虽然Android的Dalvik虚拟机优化了内存回收机制,但我们也要关注你的app的内存分配和释放。

为了垃圾回收器能回收你系统的内存,你应该避免引起内存泄露(通常由全局成员hold了对象引用),而且要在合适的时间点(如生命周期回调时,这将在后面章节进一步讨论)释放被引用的对象。

对于大多数的app来说,Dalvik虚拟机的垃圾回收器帮你进行了内存回收和管理:当响应的对象超出应用范围时回收和释放。

本文描述了Android如何管理app进程和内存分配,以及当你进行android开发时如何减少内存使用。至于Java里通用的资源释放方式请参看其他相关文档。如果你需要分析你的app的内存使用情况,请参考 Investigating Your RAM Usage。

How Android Manages Memory

Android内存模型并没有交换空间(swap space)的概念,而是使用分页(paging)和内存映射(memory-mapping)管理内存,这意味着不管是分配新的对象还是使用已有的映射页 这些内存仍然被占据在RAM里而不能被扇出。因此完全释放你app内存的唯一方式是释放对象引用以便于能被垃圾回收器回收。这有个例外:当加载没有修改的文件例如代码进入RAM时,该文件占用的内存空间能被RAM回收。

Sharing Memory

为了满足每个app对RAM的需要,Android在进程间共享RAM分页。通常遵循下面方式实现:

1.每一个app进程被一个存在的主进程(Zygote)调起。当系统启动、加载android框架代码和资源(例如activity themes)时Zygote进程启动。为了开启一个新的app进程,系统调用Zygote进程在一个新的进程里加载和运行app的代码。这种机制就是Linux系统的fork机制。这使得框架代码和资源能分配比较多的RAM内存分页,同时也使所有的app进程同享这片内存区。

2.大多数静态数据被映射到进程中。这不仅让同样的数据在进程间共享,也允许在需要的时候被调出。静态数据包括:Dalvik代码(指pre-linked的.ODEX文件),应用程序的资源(被组织成资源映射表的结构,在打包成apk时优化和对齐res资源)及native代码如.so文件。

3.一些情况下,Android使用显示分配的共享内存区域(通过ashmem或者aralloc分配)在进程间共享相同的动态RAM。例如:Window surfaces在app和screen compositor之间使用共享内存,游标缓存在content provider和client之间共享内存。

Allocating and Reclaiming App Memory

以下是一些android分配和回收内存的考量:

1.android会为每个进程在dalvik虚拟机里分别开辟heap空间。同时定义了每个heap的逻辑大小,以后可以按需增长(当然增长到系统为每个app定义的最大size为止)。

2.heap的逻辑大小和实际使用的物理内存大小是不相同的。当观察你的app的heap的时候,你会看到一个叫做Proportional Set Size (PSS)的值,它是通过共享给其他进程的page页大小计算出来的值。但这仅仅是共享给其他app内存的一个百分比,系统认为PPS总大小是你的app所占用的物理内存大小。更多的关于PPS信息,请参考 Investigating Your RAM Usage。

3.android不会进行碎片整理以释放heap空间,android只会压缩栈底未被使用的逻辑heap空间。但这并不意味着heap空间的物理内存不能被压缩。垃圾回收之后,Dalvik遍历heap,找到不被使用的pages,将这些pages返回给内核。因此,大块成对的分配和释放应该能回收所有(或几乎所有)使用的物理内存。然而,从较小的分配回收内存可能效率低得多,因为用于小分配的页仍然可以被引用和共享,尚未被释放。

Restricting App Memory

为了实现多任务功能,Android限制了分配给每个app的heap大小,上限大小在各个设备之间差别较大,取决于该设备的总体可用的有效RAM。如果你的app内存使用已到达了heap的容量,Dalvik将试着分配更多的内存。直到产生OutOfMemoryError

你可能想要知道你的手机设置给每个app到底有多大的heap空间。例如,想知道缓存多少数据是安全的。你可以通过调用ActivityManager里的getMemoryClass()查询系统这个数字,它将返回一个以Mb为单位的整数,标识你的应用程序的heap大小。下面章节将详细讨论,请参见 Check how much memory you should use

Switching Apps

Android不是用交换空间(swape space)在app之间切换。Android按最近使用(LRU)策略在缓存中保存后台进程(用户不可见进程)。例如,当用户开启一个app时,系统为这个app产生一个进程。但当用户将这个app退到后台时,这个进程并没有退出。系统持有这个进程缓存。当用户又重新进入该app时,该进程将被重用,以便于更快的app切换

如果你的app有缓存进程,它占用了一些当前它并不需要的内存。这将制约你的系统的整体性能。因此,%当系统内存吃紧时,系统将按LRU原则杀死后台进程。同时也考量哪些进程最占用内存

为了让你的app进程更长的缓存在内存而不被杀死,参见When to release your references章节的建议。

当app进程从前台到后台时怎么被缓存以及android决定杀死哪一个后台进程,更多的信息请参考Processes and Threads(http://developer.android.com/guide/components/processes-and-threads.html)

How Your App Should Manage Memory

你应该在整个开发阶段都考虑RAM的因素,包括开始开发之前的设计阶段。你能设计多种实现方式,选择其中最节省内存最有效的方式去编写代码。当你开发和实现你的app时,你应该使用下面的技术确保你的app能更有效的使用内存

慎用Service

如果你的app需要一个Servcie运行后台任务时,当执行完该任务后就停止该服务。特别注意不要你的service任务已完成,而不去停止它。

当你使用一个Service时,系统会尽量的保证该Service运行。这就使得进程占用了一部分内存。而该部分内存不能被释放。这导致了系统在LRU缓存区缓存进程数的减少。这也使得app切换更耗时,当系统内存紧张时它甚至能导致系统宕机,并杀死后台正在运行的service。

限制你Service生命周期的最好的方式是使用IntentSerivce。当IntentService处理完开启它的Intent时,它会自会关闭。更多的信息,请阅读Running in a Background Service

当一个service不需要而还在后台运行时,这是最消耗内存的内存管理错误。因此要慎用服务,当服务完成后台任务时要记得关闭。如果不这样做,由于RAM的限制,你的app运行将变得非常卡,用户也将发现app错误的行为,最后卸载你的应用。

Release memory when your user interface becomes hidden

当用户跳到一个不同的app,而使你的app的UI不可见时,你应该释放那些仅仅被你的UI使用的资源。及时的释放你的UI资源能显著的增加你的缓存进程容量。这对用户体验有显著的影响。

为了接受到用户退出的UI的通知,你需要实现在你的Activity里实现onTrimMemory回调。你应该使用该方法监听TRIMMEMORYUIHIDDEN级别的UI隐藏,TRIMMEMORYUIHIDDEN表明你的UI现在被隐藏了,你应该仅仅释放被你的UI使用的资源。

注意在TRIMMEMROYUI_HIDDEN级别下,只有当你的应用进程的所有UI组件相对于用户不可见时才回调onTrimMemory()。这不同于Activity的onStop()方法回调。onStop在activity实例不可见时才回调。这发生在app里从一个activity跳到另一个activity时。因此,虽然这时你应该重写onStop方法,在该方法里做释放资源(例如网络连接、注销广播等)的工作。但这时你不应该OnStop里做释放UI资源工作。这确保了如果用户从activity返回到先前的activity时,你的UI资源是仍然有效的,以便于快速resume你的activity。

Release memory as memory becomes tight

在app的任何生命周期阶段,onTrimMemory() 回调方法都可以告诉你你的设备的内存什么时候越来越低。你可以根据该方法传递的内存紧张级别参数来释放资源

  • TRIMMEMORYRUNNING_CRITICAL 应用处于运行状态并且认为不能被杀掉, 而设备可以使用的内存比较低, 这时系统级会按LRU策略杀掉一些其它的缓存应用。
  • TRIMMEMORYRUNNING_LOW 应用处于运行状态并且认为不能被杀掉, 而设备可以使用的内存非常低, 可以把你的application不用的资源释放一些已提高系统性能(这会会直接影响到你的程序的性能)
  • TRIMMEMORYRUNNING_CRITICAL 应用处于运行状态但是系统已经把LRU缓存里的大多数进程给杀掉了, 你必须释放掉不是非常关键的资源, 如果系统不能回收足够的运行内存, 系统会清除所有缓存应用并且会把正在活动的应用杀掉(例如正在运行的Service)。

还有,当你的app进程被系统缓存时,你可能会在onTrimMemory()里收到下面的几个内存级别:

  • TRIMMEMORYBACKGROUND 系统处于低内存的运行状态中并且你的应用刚进入LRU缓存. 虽然你的应用不会处于被杀的高风险中, 但是系统已经开始清除缓存列表中的其它应用, 所以你必须释放容易恢复的资源使你的应用继续存留在列表中以便用户再次回到你的应用时能快速恢复。
  • TRIMMEMORYMODERATE 系统处于低内存的运行状态中并且你的应用处于缓存应用列表的中部. 如果系统运行内存有限, 你的应用有被杀掉的风险.
  • TRIMMEMORYCOMPLETE 系统处于低内存的运行状态中如果系统现在没有回收足够的内存,你的应用将会第一个被杀掉. 你必须释放掉所有非关键的资源从而恢复的应用.因为 onTrimMemory() 是在android API 14中加入的, 所以低版本可以使用 onLowMemory() 方法替代, 该方法大致相当于 TRIMMEMORYCOMPLETE 事件。

注意: 当系统开始清除缓存应用列表中的应用时, 虽然系统的主要工作机制是自下而上, 但是也会通过杀掉消费大内存的应用从而使系统获得更多的内存,所以在缓存应用列表中消耗更少的内存将会有更大的机会留存下来以便用户再次使用时进行快速恢复.

Check how much memory you should use

前面提到, 不同的android设备系统拥有的运行内存各自都不同, 从而不同的应用堆内存的限制大小也不一样. 你可以通过调用 ActivityManager 中的 getMemoryClass() 函数以兆为单位获取当前应用可用的内存大小, 如果你想获取超过最大限度的内存则会发生 OutOfMemoryError。

特别地, 可以在 manifest 文件中的 标签中设置 largeHeap 属性的值为 "true"时, 当前应用就可以获取到系统分配的最大堆内存。如果你设置了该值, 可以通过 ActivityManager getLargeMemoryClass() 函数获取最大的堆内存。然而, 提供给app获取最大的heap的能力是因为确实有小部分应用需要消耗大的堆内存(比如大照片编辑应用). 从来不要仅仅是因为你的app内存溢出了就简单的请求最大的heap,内存溢出时,你要做的是快速定位内存泄露点并恢复它,只有当你的内存确实需要很大的heap空间而且不存在内存泄露时,你才需要设置largeHeap 属性的值为 "true",即使这种情况下,你也应该尽可能的避免这种需求. 因为使用大量内存后, 当你切换不同的应用或者执行其它类似的操作时, 长时间的内存回收会导致系统的性能下降 从而渐渐的会损害整个系统的用户体验。

另外, 大内存在不同的设备并不相同. 当app跑在有运行内存限制的设备上时, 大内存和正常的堆内存是一样的. 那即是设置largeHeap 属性的值为 "true可能并不起作用,所以如果你设置了largeHeap 属性的值为 "true, 你也应该调用 getMemoryClass() 函数查看正常的堆内存的大小并且尽可能使内存使用情况维护在正常堆内存之下。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 质问 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • How Android Manages Memory
    • Sharing Memory
      • Allocating and Reclaiming App Memory
        • Restricting App Memory
          • Switching Apps
          • How Your App Should Manage Memory
            • 慎用Service
              • Release memory when your user interface becomes hidden
                • Release memory as memory becomes tight
                  • Check how much memory you should use
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档