分析并优化 Android 应用内存占用

官方视频翻译,如需转载,请注明做者:Yuloran (t.cn/EGU6c76)java

演讲人介绍

Rechard Uhler,Android Runtime 开发工程师。为便于写做,笔者将以第一人称视角对视频内容进行概述。android

视频地址web

前言

想要进行内存优化,就必须对 Android 内存管理机制有比较深刻的了解,这样才能保证应用在低端机上也能有良好的表现。不一样的内存类型,包括 Shared Memory,Dex Memory 以及 GPU Memory, 都会对用户体验产生影响。shell

我在过去的三年时间里,都在致力于深刻理解 Android 应用内存管理机制。那么,为何 App 开发工程师也要关注内存占用呢?于我而言,主要是由于 Android 生态系统。若是一个 Android 应用在低端设备上用户体验很差(好比常常卡顿),那么 OEM(Original Entrusted Manufacture) 就不肯再生产这样的设备,进而致使这部分用户被排除在 Android 生态系统以外。编程

本次课题主要讨论三点内容:缓存

  • 低内存时 Android 系统的工做机制bash

  • 如何评估应用内存使用状况架构

  • 如何减小应用内存占用app

低内存时 Android 系统的工做机制

首先,须要介绍物理内存的概念,而后引入 Android Low Memory Killer。ide

物理内存

设备的物理内存被分为不少页(Page),每页 4KB。不一样的页用来作不一样的事情:

橘黄色的是已使用页,黄色的是缓存页(数据在磁盘上有备份,因此 Cache Pages 是能够被回收的),绿色的是空闲页。

用于回收 Cached pages 的 kswapd 进程:

这是一个 2G 内存的手机,X 轴表示使用时间,Y 轴表示内存使用状况。随着打开的应用愈来愈多,Used Pages 也愈来愈多,而 Cached Pages 和 Free Pages 则愈来愈少。当 Free Pages 低于 kswapd 的阈值时,Linux 内核就会经过 kswapd 进程对 Cached Pages 进行回收。当应用再次访问 Cached Pages 上的内容时,就须要从磁盘上从新加载。若是 Cached Pages 太少的话,设备就可能死机:

因此,在 Android 上咱们有个机制叫 Low Memory Killer,当 Cached Pages 太少时,就会被触发。它的工做方式是根据进程的优先级,选择性地杀死某个进程,释放该进程占用的全部资源以知足内存分配须要:

如上图所示,当 Cached Pages 低于 LMK 阈值时,将会触发低内存杀死机制。

LMK(Low Memory Killer)

若是 LMK 杀掉的是用户正在交互或能够感知的进程,将会致使很是不友好的用户体验。因此 Android SystemServer 进程维护了一张进程优先级列表,LMK 根据这张表来决定先杀死哪一个进程:

  • Perceptible 指的是非用户直接交互的进程,好比在后台播放音乐的音乐播放器进程;
  • Previous 指的是切换至当前前台应用前的应用进程;
  • Cached 指缓存的进程,这多是退至后台的应用进程,也多是已经退出的应用进程,目的是为了实现应用间的快速切换。因此,Cached 进程也是优先级最低的进程:

如上图所示,当已用内存超过 LMK 阈值时,LMK 将从 Cached 列表底部开始杀死进程。若是可用内存仍是不知足分配须要,那么将会按照上表所示优先级自底向上杀死进程,直到准备 Kill SystemServer 进程,这将致使手机重启。

因此,你能够想象 LMK 在低内存手机上的情景:

如上图所示,LMK 将一直处于活跃状态,具体表现就是应用卡顿、桌面黑屏重启,手机死机等等。如此,OEM 将不肯生产这些设备。

评估应用内存使用状况

那么,咱们怎么知道 App 使用了多少内存呢?

物理内存追踪

以前提到,设备的物理内存被分为不少页(Page),Linux Kernel 将会持续跟踪每一个进程使用的 Pages,因此只要对进程使用的 Pages 进行计数便可:

但实际状况远比这要复杂的多,由于有些 Pages 是进程间共享的:

共享内存页计数方法:

(1)RSS(Resident Set Size):App 彻底负责:

(2)PSS(Proportional Set Size):App 按比例负责,好比下图所示两个进程共享,那就负责一半。若是三个进程共享,那就负责三分之一:

(3)USS(Unique Set Size):App 无责:

但实际上,至少须要系统级别的上下文才能知道识别 RSS 与 USS。因此一般都是使用 PSS 来计算,这也能够避免多计或者少计 Shared Pages。你可使用:

adb shell dumpsys meminfo -s [process] 
复制代码

命令来查看一个进程的 PSS 使用状况:

最底部的 TOTAL 表明的就是应用按比例占用的总内存大小。

应用内存占用分析

若是想要应用支持的功能越多,UI 越炫酷,那就须要更多的内存分配。既想马儿跑,又想马儿不吃草的事情是不存在的:

内存占用影响因素:

(1)应用使用场景:很好理解,哪一个页面比较炫、动效多、或者使用了 webview,那这个时候 App 占用的内存就高:

(2)平台配置:很好理解,好比手机的分辨率越高,相同 dp 的图片占用的内存就越大,因此高档手机上,App 的内存占用确定比低档手机高:

(3)设备内存压力:设备内存越紧张,越可能触发 GC,致使 App 占用内存比设备内存充裕时低:

因此,你应当在相同的内存压力下评估你的 App 内存占用:

因为内存压力很差控制,因此建议评估前,先一键清理全部进程,而后再测试。

减小应用内存占用

使用 Android Studio 的 Memory Profiler,能够查看当前 Java 堆上分配了哪些对象、对象大小以及对象引用链和被引用链等不少信息。Live Allocation 中有 image heap、zygote heap、app heap 等能够选择,可是我建议你只关注 app heap。由于 image heap 和 zygote heap 是 App 启动时从系统继承过来的,对于这部份内存占用,咱们基本上无能为力:

关于 Memory Profiler 的细节我不会讲太多,由于明天中午 12:30 Esteban 将会详细讲解 Profiler 的用法,毕竟这是他们团队开发的。因此,我强力推荐大家也参加一下明天的宣讲会。

Java Heap 之外的内存占用分析

上面提到,TOTAL 是 PSS,那么这张图中,除了 Java Heap,其它的是什么意思呢?对于这部份内存占用,咱们又能作什么呢?

这就比较好玩了,由于这部分大可能是由 Android 平台产生的,若是你真的想理解他们,那么你须要学习不少专业知识。好比 Framework 是如何实现 View 系统及 Resource 管理的,Native Code 是如何执行的,WebView 是如何工做的,Android Runtime 是如何执行你的代码的,HAL 如何管理你的 Graphics 以及 Linux 内核的虚拟内存管理方式等等。

顺便说一下,我生活在这儿,这个橘黄色的方块里(Android Runtime):

Android 平台产生的内存占用诊断

那么,对于平台产生的内存占用,咱们须要使用工具来诊断吗?首先,咱们可使用:

adb shell dumpsys meminfo -a [process]
复制代码

来查看更详细的信息(如下数据为笔者本身开发的 App 的内存占用状况):

Applications Memory Usage (in Kilobytes):
Uptime: 498024399 Realtime: 1230430304

** MEMINFO in pid 10898 [com.yuloran.wanandroid_java] **
                   Pss      Pss   Shared  Private   Shared  Private  SwapPss     Heap     Heap     Heap
                 Total    Clean    Dirty    Dirty    Clean    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    35822        0      824    35764       32       24     8740    75776    38786    36989
  Dalvik Heap     4001        0      304     3552       72      412      240     6847     3424     3423
 Dalvik Other     5256        0       48     5256        0        0        0                           
        Stack      120        0        4      120        0        0        0                           
       Ashmem      130        0        4      128        4        0        0                           
      Gfx dev     2596        0        0     2596        0        0        0                           
    Other dev       16        0      104        0        0       16        0                           
     .so mmap    23782    22188     1132      504    13320    22188       15                           
    .jar mmap       68        0        8       68        0        0        0                           
    .apk mmap     8029       24        0     7684     1872       24        0                           
    .ttf mmap      223       20        0        0      956       20        0                           
    .dex mmap    21974    19864        0       20    13080    19864        0                           
    .oat mmap      377       64        0        0     3620       64        0                           
    .art mmap     6547      404      868     5852     7584      404       24                           
   Other mmap      408        0       12        8      644      376        0                           
   EGL mtrack    24660        0        0    24660        0        0        0                           
    GL mtrack     4524        0        0     4524        0        0        0                           
      Unknown     2130        0      184     2124        0        0        0                           
        TOTAL   140702    42564     3492    92860    41184    43392       39    82623    42210    40412
 
 Dalvik Details
        .Heap     3308        0        0     3308        0        0        0                           
         .LOS       42        0       16       12        4       28        4                           
 .LinearAlloc     4020        0       20     4020        0        0        0                           
          .GC      384        0       16      384        0        0        0                           
    .JITCache      596        0        0      596        0        0        0                           
      .Zygote      583        0      288      164       68      384        0                           
   .NonMoving       68        0        0       68        0        0        0                           
 .IndirectRef      256        0       12      256        0        0        0                           
 
 App Summary
                       Pss(KB)
                        ------
           Java Heap:     9808
         Native Heap:    35764
                Code:    50436
               Stack:      120
            Graphics:    31780
       Private Other:     8344
              System:     4450
 
               TOTAL:   140702       TOTAL SWAP PSS:       39
 
 Objects
               Views:      207         ViewRootImpl:        1
         AppContexts:        3           Activities:        1
              Assets:       18        AssetManagers:        3
       Local Binders:       24        Proxy Binders:       23
       Parcel memory:        8         Parcel count:       34
    Death Recipients:        3      OpenSSL Sockets:        0
            WebViews:        0
 
 SQL
         MEMORY_USED:      345
  PAGECACHE_OVERFLOW:       55          MALLOC_SIZE:      117
 
 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       20             41        17/38/5  /data/user/0/com.yuloran.wanandroid_java/databases/app_database.db
         4       12                         0/0/0    (attached) temp
         4       20             40         3/19/4  /data/user/0/com.yuloran.wanandroid_java/databases/app_database.db (1)
复制代码
  • Private Dirty Memory 相似于以前说过的 Used Memory;
  • Private Clean Memory 相似于 以前说过的 Cached Memory。

下面又介绍了几种工具,showmap、ahat、debug malloc等,略。。。由于他下面说到:

总的来讲就是:能够,但不必。由于这须要了解不少专业知识,并且不少数据是可见但不可控的。

内存优化建议

(1)优化 Java 堆上的对象:

不少内存虽然不在 Java 堆分配,可是其生命周期跟 Java 堆上分配的对象相绑定:

因此,优化 Java Heap 上的对象,也有助于其它类型内存的回收。

(2)减少 apk 体积:

由于不少在 apk 中占据磁盘空间的文件,在运行期也会占据内存空间:

由于 apk 占据的磁盘空间大小是固定的,因此压缩 apk 大小比下降内存占用更容易。更多 apk 大小优化方法请查看 Best Practices to Slim Down Your App Size

结语

本期视频主要讲述了 Android 的 Low Memory Killer 机制、如何评估应用的内存使用状况以及如何减小应用内存占用,来源于 Google Android Runtime 开发工程师 Rechard Uhler 的经验总结,能够说很靠谱了。

就笔者自身的开发经验来看,内存泄露比较容易解决,只是有的泄露是因为第三方 SDK 或者 Framework 致使的,此时只能经过反射来修复。若是反射也修复不了,可是不存在持续泄露,即仅泄露一次,也能够不做处理,或者经过商务推进去解决。而减小内存占用则比较困难,毕竟要想 App 功能丰富,那势必会占用更多的内存。并且如今不少项目是多人团队开发,每一个人可能只负责一小块,对整个应用的掌控能力不足,进行内存调休就更困难了。因此,内存调优工做须要丰富的编程经验及架构经验,除了 Java 之外,还须要对 Android 的不少 UI 控件有比较深刻的理解,由于在 Android 平台上,内存占用大头永远是 UI,主要是 Bitmap。

内存优化,任重而道远。

相关文章
相关标签/搜索