5. Android 内存管理

作者: Wenchao | 来源:发表于2018-07-11 21:44 被阅读0次

    Android的内存被多个进程共享。一个进程使用了多少的内存并不能一眼就看出。Android内存管理比较复杂,但是你可以在继续阅读之前可以参考Google IO上的视频讲座

    Android上的分页

    分页技术的详细介绍需要参考操作系统的相关知识,wikipedia链接。简单来讲,分页的技术是将主存的内存移动到二级存储,或者将二级存储的内存移动到主存。

    Android在移出分页到硬盘的时候,并没有使用到交换空间(swap space),这造成了很难观测到整个的内存消耗,尤其是Android中的每个应用都在一个Dalvik虚拟机上的独立进程上运行。

    分页技术 vs 交换空间

    Android用到了分页技术,但是没有使用交换空间。分页技术非常依赖内存映射(mmap())文件和将内核页按需存储到数据中。尽管这种情况不会经常发生,当内存不足的时候,分页需要减少内核分页,系统减少缓存的分页文件。Android在将脏的内存页移除的时候并没有使用交换空间,因为如果这样,在移动设备上回导致电池消耗并且导致内存过多的创建和销毁。

    机载闪存

    Android设备通常搭载的闪存比较小,只有有限的空间来存储数据。虽然这部分空间主要是用来存储应用数据,但是也可以用来存储交换文件。机载闪存比较慢而且相对于硬盘或者闪存设备而言访问稳定性也更差。尽管机载的闪存最近有了 很大的提升,但是还不足以有效承载交换空间的功能。一个简单的实践规则而言,对于1~2MB的RAM而言,交换文件大小应该差不多512MB。你可以修改内核.config(CONFIG_SWAP)然后自己编译内核,这部分的内容已经超出了这篇指南的范围。

    内存消耗的限制

    怎么知道在Android启动内存杀手关掉进程之前,你的app能够使用多少内存呢?很不幸,这个问题没有简单的答案,找到这个问题的答案需要涉及到非常多的剖析工作,使用dumpsys,procrank和Android Studio来分析才能知道。

    许多不同的因素也会影响到对Android内存消耗的测量,如:

    • 对于低、中、高设备不同的平台配置
    • 在同一台测试设备上安装不同的操作系统OS
    • 当你测量内存处在应用的不同阶段
    • 整体的设备内存压力

    所以在测量内存的时候,就需要保证在同一份代码,相同的平台配置,相同的操作系统版本,以及设备内存压力相等的情况。

    低/高内存压力

    一个剖析内存比较好的方法是确保设备有足够的内存空余(低内存压力)的时候对应用进行消耗内存的剖析。如果设备没有足够空闲内存,很难得到稳定的测试结果。需要记住的是,尽管你尝试使用剖析工具找到引起高内存的原因,但是同样也会受到硬件条件的限制。如果系统正在调整内存缓存的时候,这时候剖析的结构也是不稳定的。

    Dumpsys

    如果你想要每个进程消耗的实际的RAM,然后将所有的进程RAM加起来,最后的数字肯定会比实际上总的RAM要高。通过dumpsys,你可以得到关于每个Java进程更加清晰的信息。dumpsys提供的统计信息包含关于这个app内存的很多信息。dumpsys是运行在设备上,转储系统服务和应用的状态信息的Android工具。dumpsys允许你轻松获取系统信息。

    • 获取到的系统信息以字符串的形式呈现
    • 使用CPU,RAM,电池和存储转储的信息来检查应用如何影响整个设备。

    下面的命令会列出dumpsys找到的所有服务:

    ~$ adb shell dumpsys | grep "dumpsys services"
    

    在Android上,你可以使用dumpsys meminfo来查看Android的系统内存。

    dumpsys meminfo

    abd提供了许多工具来获取Android上运行的应用信息。最常用的方式是获取总览,使用adb shell dumpsys meminfo命令。它会提供关于每个Java进程的内存使用,原生堆,二进制数据以及很多进程和系统信息。下面的命令会提供系统内存的快速总览:

    ~$ adb shell dumpsys meminfo
    

    也可以通过namebundle ID或者pid来追踪单个进程。例如如果想要追踪Unity的androidtest这个应用的信息,可以使用下面的命令。androidtest是一个空的Unity工程,只有一个空场景,没有天空盒和任何内容,只是作为内存的基准线。

    ~$ adb shell dumpsys meminfo com.unity.amemorytest
    

    测试打印的结果如下(测试平台:Nexus 6P 2560 by 1440 px,Android 8.1.0,Unity 2018.1)

    * Applications Memory Usage (in Kilobytes):  
    * Uptime: 6815563691 Realtime: 10882940478  
    *   
    * ** MEMINFO in pid 20676 [com.unity.androidtest] **  
    *                    Pss  Private  Private  SwapPss     Heap     Heap     Heap  
    *                  Total    Dirty    Clean    Dirty     Size    Alloc     Free  
    *                 ------   ------   ------   ------   ------   ------   ------  
    *   Native Heap    31467    31448        0        0    51072    47261     3810  
    *   Dalvik Heap     1872     1760        0        0    12168     7301     4867  
    *  Dalvik Other      470      460        0        0                             
    *         Stack      492      492        0        2                             
    *        Ashmem        8        0        0        0                             
    *       Gfx dev     3846     2036        0        0                             
    *     Other dev        4        0        4        0                             
    *      .so mmap    17760      516    15908      161                             
    *     .jar mmap        4        0        4        0                             
    *     .apk mmap      243        0        0        0                             
    *     .dex mmap      116        4      112        0                             
    *     .oat mmap     6206        0     3244        0                             
    *     .art mmap     2571      716      232       22                             
    *    Other mmap       49        4        0        2                             
    *    EGL mtrack    99840    99840        0        0                             
    *     GL mtrack    64480    64480        0        0                             
    *       Unknown     1270     1264        0       14                             
    *         TOTAL   230899   203020    19504      201    63240    54562     8677  
    *    
    *  App Summary  
    *                        Pss(KB)  
    *                         ------  
    *            Java Heap:     2708  
    *          Native Heap:    31448  
    *                 Code:    19788  
    *                Stack:      492  
    *             Graphics:   166356  
    *        Private Other:     1732  
    *               System:     8375  
    *    
    *                TOTAL:   230899       TOTAL SWAP PSS:      201  
    *    
    *  Objects  
    *                Views:        7         ViewRootImpl:        1  
    *          AppContexts:        2           Activities:        1  
    *               Assets:        2        AssetManagers:        2  
    *        Local Binders:       16        Proxy Binders:       21  
    *        Parcel memory:        5         Parcel count:       23  
    *     Death Recipients:        1      OpenSSL Sockets:        2  
    *             WebViews:        0  
    *    
    *  SQL  
    *          MEMORY_USED:        0  
    *   PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0  
    *
    

    如果在一个包含完整的3D场景的应用,使用同样的命令会得到更多的信息:

    * Applications Memory Usage (in Kilobytes):  
    * Uptime: 6823482422 Realtime: 10890859209  
    *   
    * ** MEMINFO in pid 22903 [com.unity3d.androidtest] **  
    *                    Pss  Private  Private  SwapPss     Heap     Heap     Heap  
    *                  Total    Dirty    Clean    Dirty     Size    Alloc     Free  
    *                 ------   ------   ------   ------   ------   ------   ------  
    *   Native Heap   304918   304900        0        0   327552   315885    11666  
    *   Dalvik Heap     1240     1096        0        0    11858     7127     4731  
    *  Dalvik Other      424      412        0        0                             
    *         Stack      528      528        0        1                             
    *        Ashmem        6        0        0        0                             
    *       Gfx dev   196934   132128        0        0                             
    *     Other dev        4        0        4        0                             
    *      .so mmap    23976      668    21920      199                             
    *     .apk mmap      368        0        0        0                             
    *     .dex mmap      116        4      112        0                             
    *     .oat mmap     6060        0     3768        0                             
    *     .art mmap     2774      604      332       25                             
    *    Other mmap       44        4        0        2                             
    *    EGL mtrack    21600    21600        0        0                             
    *     GL mtrack   384184   384184        0        0                             
    *       Unknown     6577     6568        0       17                             
    *         TOTAL   949997   852696    26136      244   339410   323012    16397  
    *    
    *  App Summary  
    *                        Pss(KB)  
    *                         ------  
    *            Java Heap:     2032  
    *          Native Heap:   304900  
    *                 Code:    26472  
    *                Stack:      528  
    *             Graphics:   537912  
    *        Private Other:     6988  
    *               System:    71165  
    *    
    *                TOTAL:   949997       TOTAL SWAP PSS:      244  
    *    
    *  Objects  
    *                Views:        7         ViewRootImpl:        1  
    *          AppContexts:        3           Activities:        1  
    *               Assets:        2        AssetManagers:        2  
    *        Local Binders:       15        Proxy Binders:       20  
    *        Parcel memory:        3         Parcel count:       14  
    *     Death Recipients:        0      OpenSSL Sockets:        0  
    *             WebViews:        0  
    *    
    *  SQL  
    *          MEMORY_USED:        0  
    *   PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0  
    *
    

    下面的表格比较了两份数据的主要差异和详细的原因:

    Area Empty Scene [MB] Full Scene [MB] Description
    Pss 230 949 Proportional set size是内核计算的度量,它将内存共享考虑在内。系统会根据内存页中的其他进程使用的比例动态调整每个RAM内存页。对于共享内存页而言,系统会将共享的内存页大小乘上这个进程在所有使用这个内存页的总进程数所占的比例。所有的私有内存页贡献100%的大小,共享内存则贡献了(共享的内存页大小)/(共享进程数)。例如,一个在两个进程间共享的内存页像每个进程的Pss贡献了其大小的一半。按照这种方式你就可以将所有进程的Pss数据相加得到总共的RAM消耗。在进程间比较Pss也能够大致比较两者权重。
    Private Dirty 203 825 最有意思和最昂贵的度量应该是Private Dirty,这部分代表着进程内不能用磁盘上的数据备份,所以不能被分页到硬盘,也不能和其他的进程共享的内存。另一个角度理解,就是这部分的消耗内存只有当应用被销毁的时候才会被系统回收。回收之后,立刻被分配到缓存和其他地方使用,因为系统要充分利用有限的内存
    Native Heap 51 328 原生堆代表进程自身使用的内存,例如Unity Engine Code, Native C malloc, 和 Mono VM。
    Dalvik Heap 12 12 Dalvik Heap代表Dalvik虚拟机分配的内存,例如Unity中Java形式的Android代码中的变量。
    Dalvik Other 0.4 0.4 JIT和Android GC使用的内存
    Clean Memory 19 26 Android会在几个进程(例如公共框架)之间共享内存页。只要一个页内的内存改变,系统必须要写入而且将内存块标记为脏。然而,clean memory代表当从磁盘载入的时候还没有改变的内存。只有发生改变,内存标记成脏内存。
    Swapped Dirty 0.2 0.2 应用使用Dirty Memory来进行计算任务。Android没有交换区机制,所以Dirty Memory也是RAM部分,只有当应用退出的时候才会被释放。尽管如此,Swapped Dirty在部分Android设备上被用来做remap,但是也是交换到RAM而不是闪存。在Android上,这个部分和Linux类似,ZRAM可以压缩页,Linux内核将这些部分放到特殊的RAM区域,当需要的时候再次解压。
    EGL mtrack 99 22 这个部分是gralloc的内存使用。主要是SurfaceViewTextureView的总和。它也包括了帧缓冲区,因此大小也会取决于framebuffers的尺寸。支持的屏幕分辨率越高,EGL mtrack的数目越高。在这个测试中,帧缓冲区的分辨率被降低了来确保比较好的性能。降低帧缓存的大小也会降低这些缓存需要的内存量。
    GL mtrack
    &
    Gfx dev
    69 581 GL和Gfx是驱动反馈的GPU内存,主要是GL纹理大小的总和,GL命令缓冲区,固定的全局驱动RAM消耗以及Shader。需要指出,这些不会出现在旧的Android版本上。注意:客户空间驱动和内核空间驱动共享同一个内存空间。在某些Android版本上,这个部分会被重复计算两次,因此Gfx dev要比实际上使用的数值更大。
    Unknown 1.3 6.5 Unknown值得是系统不能确定分页属于上面的那种。这个部分包括原生分配或者运行时metadata,因为Adress Space Layout Randomization,这个工具没办法确定内存。Private Dirty是只用于自己应用的未知RAM。

    procrank

    另一个可以取代dumpsys的工具是procrank,这个工具可以查看所有进程使用的内存量,从高到低列出每个进程使用的内存使用量。每个进程列出的Vss,Rss,Pss,和Uss。

    ~$ adb shell procrank
    
    * PID      Vss      Rss      Pss      Uss  cmdline 
    *  890   84456K   48668K   25850K   21284K  system_server 
    * 1231   50748K   39088K   17587K   13792K  com.android.launcher2 
    *  947   34488K   28528K   10834K    9308K  com.android.wallpaper 
    *  987   26964K   26956K    8751K    7308K  com.google.process.gapps 
    *  954   24300K   24296K    6249K    4824K  com.unity.androidmemory 
    *  888   25728K   25724K    5774K    3668K  zygote 
    *  977   24100K   24096K    5667K    4340K  android.process.acore
    
    • Vss - Virtual Set Size
      一个进程总共可以获取的寻址空间。这个指标表明一个进程关联的虚拟内存空间。
    • Rss - Resident Set Size
      一个进程分配到的物理内存页。被多个进程共享的内存页被计算多次。
    • Pss - Proportional Set Size
      和Rss类似,只不过将共享的内存页处理共享的进程数来计算。
    • Uss - Unique Set Size
      = Private Dirty的概念,表明进程内不能被分页到硬盘的数据,不能被其他进程共享的内存部分。

    meminfo

    meminfo命令给出系统总内存使用情况。

    ~$ adb shell cat /proc/meminfo
    

    前四个数值值得关注:

    * MemTotal:        2866492 kB  
    * MemFree:          244944 kB  
    * Buffers:           36616 kB  
    * Cached:           937700 kB  
    * SwapCached:        13744 kB
    
    • MemTotal 是内核和用户区可以获取的内存总量,这个值逼实际的物理RAM要小,因为GSM,缓冲区等这些也需要消耗内存。
    • MemFree 是目前没有被使用的RAM量。在Android,这个值通常比较小,因为系统总是尝试使用所有可用内存保持进程运行。
    • Cached 是用来作为文件系统缓存的RAM。

    额外信息参照
    RAM investigation page
    Android performance guides

    Android Studio

    Android Studio除了SDK中的命令行工具,也提供了一个内存查看工具。和命令行提供的功能类似。


    Android Studio内存查看工具.png

    我们使用到的例子是在dumpsys meminfo中的空工程在Android Studio中得到的数据。基本上包括了使用dumpsys可以得到的信息,再加上一些额外信息。

    Section Size[MB] Area
    Total[mb] 168.7 All
    Others[mb] 3.1 Other dev + Unknown
    Code[mb] 28 mmaps
    Stack[mb] 0.1 Stack
    Graphics[mb] 88.7 Gfxdev + EGL mtrack + GL mtrack
    Native[mb] 40.8 Native Heap
    Java[mb] 8 Dalvik Heap

    Plugin

    通常来讲,大部分的内存消耗来自于原生堆。Dalvik堆相对于原生堆来讲很小。如果比较大,需要检查一下你使用的Android插件。原生堆很难知道内存消耗具体来自哪,也没有很好的方法在剖析器中查看分配。一个可行的方法是将每个插件独立出来,和一个空项目进行对比得到具体的 消耗。

    相关文章

      网友评论

        本文标题:5. Android 内存管理

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