【官网翻译】性能篇(七)内存管理概述

前言

       本文翻译自Android开发者官网的一篇文档,主要用于从宏观上介绍内存管理需要注意的一些要点。

       中国版官网原文地址为:https://developer.android.google.cn/topic/performance/memory-overview

       路径为:Android Developers > Docs > 指南 > Best practies > Performance > Overview of memory management

 

正文

       Android运行时ART和Dalvik虚拟机使用分页和内存映射(mmapping)管理内存。这意味着所有被修改过的内存——无论是通过分配新的对象还是触摸被映射的页——仍然驻留在RAM中并且不能移除分页。唯一从应用中释放内存的方法是释放应用持有的对象引用,让内存能够被垃圾收集器使用。但有一个例外:如果系统想在其它地方使用内存,那么所有被映射但没有被修改的文件,比如代码,可能会被从RAM的分页中移除。

       本文将阐述Android如何管理应用进程和内存分配。更多关于如何更有效管理应用内存的信息,请查阅【管理应用的内存】。

 

垃圾收集

       一个被管理的内存环境, 像ART或Dalvik虚拟机,保持追踪每一块内存分配。一旦虚拟机确定某块内存不再被程序使用,它会释放该内存回到堆中,而不需要程序员的任何干预。这个在被管理的内存环境中回收不再被使用的内存的机制被称为垃圾收集。垃圾收集有两个目标:找到程序中在未来不会再被访问的数据对象;以及回收被那些对象使用的资源。

       Android的内存堆是一个分代的内存堆,这意味着,基于被分配的对象的预期寿命和大小,它追踪着不同的分配群组。例如,最近被分配的对象属于年轻代。当一个对象保持活跃了足够长的时间,它可能会被提升到老年代,后面还有一个永久代。

       每一个堆分代都有它自己的对象可以占据的特定的内存数量上限。任何时候某代开始填充时,系统会执行一个垃圾回收事件来尝试释放内存。这个垃圾收集的持续时长取决于它所收集的对象在哪个代,以及在每个代中有多少活跃对象。

       虽然垃圾收集可能非常快,但它仍然会影响您应用的性能。通常您无法掌控代码中垃圾收集事件何时会发生。系统有一套正在运行的标准来决定何时执行垃圾收集。当满足标准时,系统会停止执行进程并开始垃圾收集。如果垃圾收集发生在一个密集的正在处理的循环(如动画或音乐回放期间)中间时,这可能会增加处理时间。这个增加可能潜在地推动应用中代码执行时间超过为有效和平滑帧渲染而建议的16ms阈值。

       除此之外,您的代码流可能执行某些类型的工作:这类工作强迫垃圾收集事件更频繁地发生或者使它们延续得比平常时间更长。例如,如果在透明度混合动画的每一帧期间,您在一个for循环最里面部分分配多个对象,可能导致大量对象污染内存堆。在那种环境下,垃圾收集器会执行多个垃圾收集事件并会降低应用的性能。

       更多关于垃圾收集的分代信息,请查阅【垃圾收集】。

 

共享内存

       为了适合在RAM中所需要的一切,Android尝试跨进程分享RAM分页。Android可以通过如下方式实现这个目的:

  • 每个应用进程都是从一个已经存在的叫做Zygote的进程fork(译者注:派生)出来的。当系统启动并且加载公共框架代码和资源(比如activity主题)时,Zygote进程开始启动。为了启动新的应用进程,系统会fork Zygote进程,然后在新的应用进程中加载和运行应用代码。这个方法允许大部分的为框架代码和资源而分配的RAM分页在所有进程之间共享。
  • 大部分静态数据被映射都一个进程中。这项技术允许数据在进程之间共享,并且允许在需要时将其调出。典型的静态数据包括:Dalvik代码(通过把它放入到一个为直接映射而预先链接的.odex文件),应用资源(通过将资源表设计为一个可以被映射的结构并通过对齐APK的zip入口),以及传统的项目元素如.so文件中的本地代码。
  • 在许多地方,通过使用被显式分配的共享内存区域(使用ashmem或者gralloc),Android跨进程共享相同的动态RAM。例如,窗口表面使用应用和屏幕合成器之间的共享内存,以及游标缓存使用内容提供者和客户端之间的共享内存。

       因为共享内存的大量使用,确定您的应用正在使用多少内存需要关注。关于正确确定应用的内存使用,在【RAM使用研究】中进行探讨。

 

分配和回收应用内存

       对于每个应用进程,Dalvik堆被限制为单个虚拟内存范围。这定义了逻辑堆的大小,这个大小可以根据需要增长,但只能增长到系统为每个应用定义的极限值。

       堆的逻辑大小和堆使用的物理内存数量并不相同。当检查应用的堆时,Android会计算一个被称为Proportional Set Size(PSS),它会计算和其它进程共享的赃页和干净页——但是其数量只能与共享RAM的应用数成比例。PSS总数被系统看成是物理内存的足迹。更多关于PSS的信息,请查阅【RAM使用研究】指导。

       Dalvik堆并不会压缩堆的逻辑大小,这意味着Android不会整理堆碎片来压缩空间。Android仅仅会在堆的结尾有未被使用的空间时压缩逻辑堆大小。但是,系统仍然会减少被堆使用的物理内存。垃圾收集发生以后,Dalvik会遍历堆并且找到未被使用的页,然后使用madvise把那些页返回到内核。所以,成对的分配和大块的重新分配可能导致回收所有(或者几乎所有)被使用的物理内存。可是,从小的分配中回收内存可能非常低效,因为用于小分配的页面可能仍然和还没有被释放的事物共享。

 

限制应用内存

       为了维护功能的多任务环境,Android对每个应用的对大小设置了一个硬性限制。依据总体上所拥有的可用RAM数量多少,确切的堆大小限制在设备之间是不同的。如果您的应用已经到达了堆的容量并且尝试分配更多的内存,可能会收到OutOfMemoryError。

       在某些情况下,您可能想查询系统来确切地知道您当前的设备有多少可用的堆空间——例如,为了确定保留多少数据在缓存中是安全的。您可以通过调用getMemoryClass()查询系统来获取这个数据。这个方法会返回一个整数来指示应用堆可用的兆字节数。

 

切换应用

       当用户在应用之间切换时,Android将不在前台的应用——也就是,对用户是不可见的或者运行一个前台service如音乐播放——保存在最近最少使用(LRU)缓存中。例如,当用户首先启动应用,就会为它创建一个进程;但是当用户离开这个应用,那个进程并不会退出。系统会缓存这个进程。如果用户稍后返回到该应用,系统会重新使用这个进程,从而让应用切换得更快。

       如果应用拥有一个缓存的进程,并且保留当前不需要的内存,那么您的应用——即使当用户不是正在使用它——会影响系统的整体性能。当系统运行时内存不足,系统会从最近最少使用的进程开始杀死LRU缓存中的应用。系统也会考虑那些持有最多内存的进程,并且可能终止它们来释放RAM。

★ 注意:当系统开始杀死LRU缓存中的进程时,主要是从下往上进行的。系统也会考虑哪些进程消耗了更多的内存,并且这样的话,如果被杀死可以提供给系统更多的内存收获。在LRU列表中消耗的内存越少,您保留在列表并且能够快速恢复的机会就越大。

       更多关于当不在前台运行时进程如何缓存以及Android如何决定哪些进程可以被杀死的信息,请查阅【进程和线程】指导。

 

结语 

       本文最大限度保持原文的意思,由于笔者水平有限,若有翻译不准确或不妥当的地方,请指正,谢谢!

 

posted @ 2019-04-23 14:20  宋者为王  阅读(781)  评论(0编辑  收藏  举报