[Android] dumpsys meminfoの見方

# [dumpsys](https://developer.android.com/studio/command-line/dumpsys)とは

Androidのsystem serviceの状態を出力するコマンド。
オプション無しだと全サービスの情報を出力してくれる。
`dumpsys -l`で対応するserviceの一覧を取得し、`dumpsys SERVICE`で特定のserviceの情報だけ出力することも可能。

dumpsys自体はserviceに対してdump要求を投げるだけで、実際の情報出力はservice側でおこなう。
この作りのおかげで柔軟にdumpsysを拡張できる。
逆に言えばdumpsysに対応するserviceはシステムごとに異なる。

以前["cpuinfo"](https://qiita.com/takeoverjp/items/25b5ceb35a27c0baea1d)、["storaged"](https://qiita.com/takeoverjp/items/cdc0c4eed520c1fda577)を取り上げたので、今回はメモリ使用量のスナップショット情報を取得する"meminfo"の中身を見る。


ソースコードおよび動作確認の環境は以下のとおり。
- Android: [android-8.1.0_r1.0](https://search.siprop.org/android-8.1.0_r1.0/), Oreo,  API 27
- Linux kernel: [android-4.4-o-release](https://android.googlesource.com/kernel/common/+/refs/heads/android-4.4-o-release)

# dumpsys meminfo

"meminfo"は、[公式ドキュメント](https://developer.android.com/studio/command-line/dumpsys#meminfo)やヘルプがある。

## 起動引数

```
$ adb shell dumpsys meminfo -h
meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
  -a: include all available information for each process.
  -d: include dalvik details.
  -c: dump in a compact machine-parseable representation.
  -s: dump only summary of application memory usage.
  -S: dump also SwapPss.
  --oom: only show processes organized by oom adj.
  --local: only collect details locally, don't call process.
  --package: interpret process arg as package, dumping all
             processes that have loaded that package.
  --checkin: dump data for a checkin
If [process] is specified it can be the name or
pid of a specific process to dump.
```

さらにヘルプに記載されていないオプションに、`--unreachable`がある。
これは`ApplicationInfo.FLAG_DEBUGGABLE`が有効な場合に`libmemunreachable`の機能を使ってメモリリークの可能性がある箇所をリストアップしてくれるという機能だ。
ご丁寧に、メモリの先頭20バイトのダンプまで出力してくれる。
なぜヘルプに記載がないのか不明だが、ぜひ覚えておきたい。

```
 Unreachable memory
  3432 bytes in 17 unreachable allocations
  ABI: 'x86'

  640 bytes unreachable at cfef6280
   first 20 bytes of contents:
   cfef6280: 47 4c 5f 4f 45 53 5f 45 47 4c 5f 69 6d 61 67 65 GL_OES_EGL_image
   cfef6290: 20 47 4c 5f 4f 45 53 5f 45 47 4c 5f 69 6d 61 67  GL_OES_EGL_imag
```

## 出力
`dumpsys meminfo`には大きくわけて２種類の情報を出力できる。
システム全体のメモリ使用状況と、特定のプロセスのメモリ使用状況だ。

### システム全体のメモリ使用状況
`[process]`を指定しなければ、システム全体のメモリ使用状況が取得できる。

```
$ adb shell dumpsys meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 185142393 Realtime: 185142393

Total PSS by process:
     78,971K: system (pid 1739)
     73,453K: com.android.settings (pid 10066 / activities)
     71,166K: com.google.android.googlequicksearchbox:search (pid 7671)
     57,813K: com.android.chrome (pid 11345 / activities)
     57,128K: com.google.android.gms (pid 2729)
     ...

Total PSS by OOM adjustment:
    102,762K: Native
         11,378K: webview_zygote32 (pid 2069)
         11,023K: zygote (pid 1575)
          8,693K: logd (pid 1346)
     78,971K: System
         78,971K: system (pid 1739)
     61,158K: Persistent
         42,552K: com.android.systemui (pid 2043)
         18,606K: com.android.phone (pid 2093)
    111,012K: Foreground
         57,813K: com.android.chrome (pid 11345 / activities)
         35,663K: com.android.chrome:sandboxed_process2 (pid 12159)
         17,536K: com.android.chrome:privileged_process0 (pid 11502)
    173,964K: Visible
         71,166K: com.google.android.googlequicksearchbox:search (pid 7671)
         49,281K: com.google.android.gms.persistent (pid 2006)
         40,302K: com.google.android.apps.nexuslauncher (pid 2599 / activities)
     96,534K: Perceptible
         57,128K: com.google.android.gms (pid 2729)
         39,406K: com.google.android.inputmethod.latin (pid 2020)
     83,369K: Previous
         73,453K: com.android.settings (pid 10066 / activities)
          9,916K: com.google.android.setupwizard (pid 7497)
     99,770K: Cached
         19,102K: com.google.android.apps.messaging:rcs (pid 12059)
         16,077K: com.google.android.calendar (pid 11846)
         14,184K: com.google.android.gms.ui (pid 12618)

Total PSS by category:
    222,003K: Native
    154,210K: .dex mmap
    114,120K: .apk mmap
     79,066K: .so mmap
     69,293K: Dalvik
     37,480K: .oat mmap
     35,128K: .art mmap
     31,648K: Dalvik Other
     22,808K: Unknown
     21,870K: Other mmap
     13,680K: Stack
      2,183K: .ttf mmap
      1,938K: Ashmem
      1,935K: Other dev
        178K: .jar mmap
          0K: Cursor
          0K: Gfx dev
          0K: EGL mtrack
          0K: GL mtrack
          0K: Other mtrack

Total RAM: 1,530,828K (status normal)
 Free RAM:   593,970K (   99,770K cached pss +   224,260K cached kernel +   269,940K free)
 Used RAM:   790,706K (  707,770K used pss +    82,936K kernel)
 Lost RAM:   146,152K
   Tuning: 384 (large 384), oom   184,320K, restore limit    61,440K (high-end-gfx)
```

- `Total PSS by process`
    - プロセス単位でPSS(Proportional Set Size)が多いものから順に表示される。
    - 1個以上のActivityを持つプロセスには`activities`と表示される。

- `Total PSS by OOM adjustment`
    - OOMのADJクラスごとのPSS値。
    - 殺されにくいクラスから順に並ぶ。
    - ADJクラスの一覧は、`dumpsys activity oom`で確認可能。
        - `()`内の値はプロセスを終了する閾値。
        - 閾値はカーネルモジュール`lowmemorykiller`のパラメータとして設定される。
        - `ActivityManager`がsocket経由で`LMK_TARGET`コマンドを設定値とともにを`lmkd`に送り、`lmkd`がカーネルモジュールパラメータを設定する。

```shell-session
$ adb shell dumpsys activity oom
  OOM levels:
    -900: SYSTEM_ADJ (   73,728K)
    -800: PERSISTENT_PROC_ADJ (   73,728K)
    -700: PERSISTENT_SERVICE_ADJ (   73,728K)
      0: FOREGROUND_APP_ADJ (   73,728K)
     100: VISIBLE_APP_ADJ (   92,160K)
     200: PERCEPTIBLE_APP_ADJ (  110,592K)
     300: BACKUP_APP_ADJ (  129,024K)
     400: HEAVY_WEIGHT_APP_ADJ (  147,456K)
     500: SERVICE_ADJ (  147,456K)
     600: HOME_APP_ADJ (  147,456K)
     700: PREVIOUS_APP_ADJ (  147,456K)
     800: SERVICE_B_ADJ (  147,456K)
     900: CACHED_APP_MIN_ADJ (  147,456K)
     906: CACHED_APP_MAX_ADJ (  184,320K)
...
$ adb shell cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906
$ adb shell cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,36864,46080
```

```java:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
    // These are the various interesting memory levels that we will give to                                                                                                                                   
    // the OOM killer.  Note that the OOM killer only supports 6 slots, so we                                                                                                                                 
    // can't give it a different value for every possible kind of process.                                                                                                                                    
    private final int[] mOomAdj = new int[] {
            FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
    };
    // These are the low-end OOM level limits.  This is appropriate for an                                                                                                                                    
    // HVGA or smaller phone with less than 512MB.  Values are in KB.                                                                                                                                         
    private final int[] mOomMinFreeLow = new int[] {
            12288, 18432, 24576,
            36864, 43008, 49152
    };
    // These are the high-end OOM level limits.  This is appropriate for a                                                                                                                                    
    // 1280x800 or larger screen with around 1GB RAM.  Values are in KB.                                                                                                                                      
    private final int[] mOomMinFreeHigh = new int[] {
            73728, 92160, 110592,
            129024, 147456, 184320
    };
```

- `Total PSS by category`
    - `/proc/[pid]/smaps`から用途ごとのメモリ使用量を全プロセス分を集計し、多いものから順に表示される。
    - `Native`: `[heap]`, `[anon:libc_malloc]`, `/dev/ashmem/libc malloc`
    - `Dalvik` or `Dalvik Other`: `/dev/ashmem/dalvik-`
        - Dalvik関連については、`-d`オプションを指定することで更に詳細な情報が出力される。
    - `Stack`: `[stack]`
    - 対応は、frameworks/base/core/jni/android_os_Debug.cpp

- `Total RAM`: `/proc/meminfo`の`MemTotal`。つまり、カーネルが認識している物理メモリの総量。
    - `status` : `ActivityManager`が各プロセスに設定するmemory trimming level。`normal`, `moderate`, `low`, `critical`のいずれか。`PROCESS_STATE_CACHED_ACTIVITY`, `PROCESS_STATE_CACHED_ACTIVITY_CLIENT`, `PROCESS_STATE_CACHED_EMPTY`のプロセス数で決定される。これらのプロセスが多いほど、プロセスを終了することでメモリを回収できるため、プロセス単位でのmemory trimming levelは低くなる。
    - `critical`: `numCached <= 5` && `numEmpty <= 8` && `numCached+numEmpty <= 3`
    - `low` : `numCached <= 5` && `numEmpty <= 8` && `3 < numCached+numEmpty <= 5`
    - `moderate` : `numCached <= 5` && `numEmpty <= 8` && `5 < numCached+numEmpty`
    - `normal` : 上記以外

```shell-session
$ adb shell dumpsys activity settings | grep CUR_TRIM
  CUR_TRIM_EMPTY_PROCESSES=8
  CUR_TRIM_CACHED_PROCESSES=5
```

```java:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
    // Threshold of number of cached+empty where we consider memory critical.
    static final int TRIM_CRITICAL_THRESHOLD = 3;

    // Threshold of number of cached+empty where we consider memory critical.
    static final int TRIM_LOW_THRESHOLD = 5;
```


- `Free RAM`: いつでも回収可能なメモリ。以下３つのメモリの和。
    - `cached pss`: いつでも殺していいプロセスのPSSの合計値。
        - `oomAdj`が`CACHED_APP_MIN_ADJ(900)`以上のプロセスのPSS。
        - `CACHED_APP_MIN_ADJ`以上ということは、裏に回っているactivityを持っているだけのプロセスを指し、終了させたとしてもユーザに影響しない。
        - なおPSSなので、該当プロセスを終了したとしても厳密にこの分だけ解放されるわけではない（他のプロセスもマップしている共有ライブラリは、それらに再分配される）。
    - `cached kernel`: カーネルでキャッシュ用途に使われているメモリのうち、プロセスにマッピングされていないメモリの量。
        - `/proc/meminfo`の`Buffers+SReclaimable+Cached-Mapped`。
    - `free`: `/proc/meminfo`の`MemFree`。つまり、まだどの用途にも使われていないメモリの量。

- `Used RAM`: 使用中のメモリ。以下２つのメモリの和。
    - `used pss`: 全プロセスのPSSの合計 - `cached pss`。DirtyなページだけでなくCleanなページも含む。
    - `kernel`: Kernelで使用中のメモリ。
        - `/proc/meminfo`の`Shmem+SUnreclaim+VmallocUsed+PageTables+KernelStack`。
        - ただし、`VmallocUsed`だけはより正確にするために`/proc/vmallocinfo`を使って再計算している。

```java:frameworks/base/core/java/com/android/internal/util/MemInfoReader.java
    /**                                                                                                                                                                                                       
     * Amount of RAM that is in use by the kernel for actual allocations.                                                                                                                                     
     */
    public long getKernelUsedSizeKb() {
        return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
                + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES]
                + mInfos[Debug.MEMINFO_KERNEL_STACK];
    }
```

```c++:frameworks/base/core/jni/android_os_Debug.cpp
static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray out)
{
...
    // Recompute Vmalloc Used since the value in meminfo                                                                                                                                                      
    // doesn't account for I/O remapping which doesn't use RAM.                                                                                                                                               
    mem[MEMINFO_VMALLOC_USED] = get_allocated_vmalloc_memory() / 1024;
...
```

- `Lost RAM`: ざっくり`TotalRAM - FreeRAM - UsedRAM` (swapありだとちょっと違うが)。
    - 本当は0になってほしいがメモリ集計外のKernelAPIでメモリを確保している場合や、複数プロセスで二重に計上してしまうメモリなどの影響で出てしまった誤差。
    - 負の値を取ることもある。
    - 例えば巨大なファイルをtmpfsに置くと、`memInfo.getFreeSizeKb(MemFree)`が減る一方で、`memInfo.getCachedSizeKb(Cached)`と`memInfo.getKernelUsedSizeKb(Shmem)`の両方が増えるため、`lostRAM`は負の値になる。

```java:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
            long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
                    - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                    - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
```

- `KSM` : KSM (Kernel same-page Merging)発生時のみ表示される。
    - `/sys/kernel/mm/ksm/`以下から収集している。

- `ZRAM`: ZRAM使用時のみ表示される。
        - AndroidではZRAMをswapとして使うことができる。
        - `/proc/meminfo`の`ZRam`（ZRAMが物理的に使用しているサイズ(圧縮後)）
        - `/proc/meminfo`の`SwapTotal-SwapFree`（スワップアウトしているサイズ(圧縮前)）
        - `/proc/meminfo`の`SwapTotal`（スワップ領域として確保されているサイズ）

- `Tuning`
- 


### 特定のプロセスのメモリ使用状況
```
$ adb shell dumpsys meminfo com.android.chrome
Applications Memory Usage (in Kilobytes):
Uptime: 188982591 Realtime: 188982591

** MEMINFO in pid 11345 [com.android.chrome] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    16013    15968        0        0    25728    22402     3325
  Dalvik Heap     2242     2200        0        0     5682     2841     2841
 Dalvik Other     1190     1188        0        0
        Stack      476      476        0        0
       Ashmem      811      128        0        0
    Other dev      227      200       24        0
     .so mmap     2565      148      284        0
    .apk mmap    22009      100    10380        0
    .ttf mmap      241        0      168        0
    .dex mmap     5445       12     2388        0
    .oat mmap     2544        0        0        0
    .art mmap     1785     1516        0        0
   Other mmap     1537        4      940        0
      Unknown      616      616        0        0
        TOTAL    57701    22556    14184        0    31410    25243     6166

 App Summary
                       Pss(KB)
                        ------
           Java Heap:     3716
         Native Heap:    15968
                Code:    13480
               Stack:      476
            Graphics:        0
       Private Other:     3100
              System:    20961

               TOTAL:    57701       TOTAL SWAP PSS:        0

 Objects
               Views:      136         ViewRootImpl:        1
         AppContexts:        3           Activities:        1
              Assets:        4        AssetManagers:        6
       Local Binders:       59        Proxy Binders:       42
       Parcel memory:       10         Parcel count:       40
    Death Recipients:        5      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0
```

## 出力フォーマット
出力フォーマットの変更も対応している。
こちらもcompact形式(`-c`)とcheckin(`--checkin`)の２種類がある。

### compact形式(`-c`)

他のプログラムで解釈しやすいようにCSV形式でコンパクトに出力する。
システム全体のメモリ使用状況を出力するときのみ有効。

```
$ adb shell dumpsys meminfo -c
version,1
time,187036613,187036613
oom,native,102696,N/A
proc,native,/init,1,1545,N/A,e
proc,native,ueventd,1052,1076,N/A,e
proc,native,logd,1346,8705,N/A,e
proc,native,servicemanager,1347,620,N/A,e
```

| 列１ | 列２ | 列３ | 列４ | 列５ | 列６ | 列７ | 説明 |
|-----|------|-----|-----|-----|------|------|-----|
| `version` | フォーマットバージョン |   |   |   |   |   |   |
| `time` | `uptime` (ミリ秒)  | `realtime` (ミリ秒) |   |   |   |   |   |
| `proc` | `tag` | `shortLabel` | `id` | `pss` | `swapPss` | `hasActivities` | `Total PSS by process`相当 |
| `oom` | `shortLabel` | `pss` | `swapPss` | | | | `Total PSS by OOM adjustment`相当 |
| `cat` | `shortLabel` | `pss` | `swapPss` | | | | `Total PSS by category`相当 |
| `ram` | `getTotalSizeKb()` (KiB) | `Free RAM` (KiB) | `used pss` (KiB) | | | | `Total RAM`, `Free RAM`, `Used RAM`相当 |
| `lostram` | `lostRAM` |  |  | | | | `Lost RAM`相当 |
| `zram` | `getZramTotalSizeKb()` | `getSwapTotalSizeKb()` | `getSwapFreeSizeKb()` | | | | `ZRAM`相当 |
| `ksm` | `KSM_SHARING` | `KSM_SHARED` | `KSM_UNSHARED` | `KSM_VOLATILE` | | | `KSM`相当 |
| `tuning` | ? | ? | ? | ? | | | `Tuning`相当 |


### checkin
```
$ adb shell dumpsys meminfo --checkin
time,186992273,186992273
4,11345,com.android.chrome,25728,5723,N/A,31451,22492,2862,N/A,25354,3235,2861,N/A,6096,16001,2762,39461,58224,0,0,13220,13220,1056,1168,5296,7520,0,0,136112,136112,15956,2720,4396,23072,0,0,14184,14184,0,0,\
0,0,0,0,0,0,Dalvik Other,1198,0,60,0,1196,0,0,0,Stack,476,0,4,0,476,0,0,0,Cursor,0,0,0,0,0,0,0,0,Ashmem,811,0,1380,12,128,0,0,0,Gfx dev,0,0,0,0,0,0,0,0,Other dev,227,0,80,120,200,24,0,0,.so mmap,2565,284,270\
8,30004,148,284,0,0,.jar mmap,0,0,0,0,0,0,0,0,.apk mmap,22009,10380,0,40384,100,10380,0,0,.ttf mmap,241,168,0,424,0,168,0,0,.dex mmap,5449,2388,0,25212,12,2388,0,0,.oat mmap,2546,0,60,31044,0,0,0,0,.art mmap\
,1786,0,808,6000,1516,0,0,0,Other mmap,1537,0,16,2912,4,940,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,0,0,0,0,0,0,0,0,Other mtrack,0,0,0,0,0,0,0,0,136,1,3,1,4,6,59,42,5,1,0,0,0,0
4,7671,com.google.android.googlequicksearchbox:search,21376,6998,N/A,28374,17573,5249,N/A,22822,3802,1749,N/A,5551,10805,5176,52874,68855,0,0,24320,24320,1060,1208,3944,6212,0,0,141848,141848,10760,5132,7972\
,23864,0,0,24556,24556,0,0,0,0,0,0,0,0,Dalvik Other,4522,0,60,0,4520,0,0,0,Stack,684,0,4,0,684,0,0,0,Cursor,0,0,0,0,0,0,0,0,Ashmem,2,0,4,12,0,0,0,0,Gfx dev,0,0,0,0,0,0,0,0,Other dev,158,0,76,120,0,156,0,0,.s\
o mmap,2031,0,2708,29844,148,0,0,0,.jar mmap,0,0,0,0,0,0,0,0,.apk mmap,12468,5288,0,32416,228,5288,0,0,.ttf mmap,84,0,0,464,0,0,0,0,.dex mmap,27569,18956,0,41460,28,18956,0,0,.oat mmap,2310,76,60,29116,0,76,\
0,0,.art mmap,1752,0,844,6000,1480,0,0,0,Other mmap,414,0,16,2416,4,80,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,0,0,0,0,0,0,0,0,Other mtrack,0,0,0,0,0,0,0,0,144,1,8,1,5,5,42,32,0,0,706,706,144,117,/data/user\
/0/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store/content_store.db,4,40,55,5/47/4,5/47/4,/data/user/0/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_st\
ore/content_store.db (2),4,40,19,0/14/1,0/14/1,/data/user/0/com.google.android.googlequicksearchbox/app_si/now_content_store/content_store.db,4,40,19,0/60/1,0/60/1,/data/user/0/com.google.android.googlequick\
searchbox/app_si/now_content_store/content_store.db (2),4,40,36,3/33/2,3/33/2
```


## 実装

使い方や出力例は公式等に任せるとして、実装を追ってみる。
`meminfo` Serviceの登録は、`cpuinfo`と同じく`AcitivityManagerService::setSystemProcess()`で行われる。

```java:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    public void setSystemProcess() {
        try {
            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
            ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
            ServiceManager.addService("meminfo", new MemBinder(this));
            ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
            ServiceManager.addService("dbinfo", new DbBinder(this));
            if (MONITOR_CPU_USAGE) {
                ServiceManager.addService("cpuinfo", new CpuBinder(this));
            }
            ServiceManager.addService("permission", new PermissionController(this));
            ServiceManager.addService("processinfo", new ProcessInfoService(this));
...
        }
    }
```

`dumpsys meminfo`実行時は、`MemBinder::dump()`が呼び出され、処理は`ActivityManagerService::dumpApplicationMemoryUsage()`に委譲される。

```java:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    static class MemBinder extends Binder {
        ActivityManagerService mActivityManagerService;
        MemBinder(ActivityManagerService activityManagerService) {
            mActivityManagerService = activityManagerService;
        }

        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                    "meminfo", pw)) return;
            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
        }
    }
```

`ActivityManagerService::dumpApplicationMemoryUsage()`の最初でオプションの処理を行う。

```java:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    final void dumpApplicationMemoryUsage(FileDescriptor fd,
            PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
...
        while (opti < args.length) {
            String opt = args[opti];
            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
                break;
            }
            opti++;
            if ("-a".equals(opt)) {
                dumpDetails = true;
                dumpFullDetails = true;
                dumpDalvik = true;
                dumpSwapPss = true;
...
```

`ActivityManagerService::dumpApplicationMemoryUsage()`はとても長い関数だが、実際にメモリ使用量を収集しているのは、下記の３箇所。

- `Debug::getMemoryInfo()`: 特定のPIDの詳細メモリ情報。`/proc/<pid>/smaps`を解析する。mapされているファイルの拡張子(`.so`, `.jar`, `.apk`等)ごとに分類。
- `Debug.getPss()`: 特定のPIDのPSS。`/proc/<pid>/smaps_rollup`を解析する。
- `IApplicationThread::dumpMemInfo()`: プロセスヒープの詳細（後述）。

```java:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    final void dumpApplicationMemoryUsage(FileDescriptor fd,
            PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
...
        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
...
            if (thread != null) {
                if (!isCheckinRequest && dumpDetails) {
                    pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
                }
...
                if (dumpDetails || (!brief && !oomOnly)) {
                    Debug.getMemoryInfo(pid, mi);
                    hasSwapPss = mi.hasSwappedOutPss;
                } else {
                    mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
                    mi.dalvikPrivateDirty = (int)tmpLong[0];
                }
                if (dumpDetails) {
                    if (localOnly) {
...
                    } else {
                        pw.flush();
                        try {
                            TransferPipe tp = new TransferPipe();
                            try {
                                thread.dumpMemInfo(tp.getWriteFd(),
                                        mi, isCheckinRequest, dumpFullDetails,
                                        dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
                                tp.go(fd);
                            } finally {
                                tp.kill();
                            }
...
                        }
                    }
                }
...
            }
        }

```

`ActivityThread::dumpMemInfo()`では、下記の情報を収集、出力する。

- Native Heap
    - Size : `mallinfo(3)`の`usmblks` (Maximum total allocated space)
    - Alloc : `mallinfo(3)`の`uordblks` (Total allocated space)
    - Free : `mallinfo(3)`の`fordblks` (Total free space)
- Dalvik Heap
    - Size : `JVM_TotalMemory()`
    - Alloc : `JVM_TotalMemory() - JVM_FreeMemory()`
    - Free : `JVM_FreeMemory()`
- Java Object個数（タイプ別）
    - この時、正確にObject個数を集計するため、**Garbage Collectionが走る**。
- SQLite使用メモリ

これらの情報と、前述の`Debug::getMemInfo`等で取得した情報を併せて出力する。



```java:frameworks/base/core/java/android/app/ActivityThread.java
        private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
            long nativeMax = Debug.getNativeHeapSize() / 1024;
            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;

            Runtime runtime = Runtime.getRuntime();
            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
            long dalvikMax = runtime.totalMemory() / 1024;
            long dalvikFree = runtime.freeMemory() / 1024;
            long dalvikAllocated = dalvikMax - dalvikFree;

```

なお、`Debug` classは下記でJNIとしてC++で実装されている。


```c++:frameworks/base/core/jni/android_os_Debug.cpp
static const JNINativeMethod gMethods[] = {
    { "getNativeHeapSize",      "()J",
            (void*) android_os_Debug_getNativeHeapSize },
    { "getNativeHeapAllocatedSize", "()J",
            (void*) android_os_Debug_getNativeHeapAllocatedSize },
    { "getNativeHeapFreeSize",  "()J",
            (void*) android_os_Debug_getNativeHeapFreeSize },
    { "getMemoryInfo",          "(Landroid/os/Debug$MemoryInfo;)V",
            (void*) android_os_Debug_getDirtyPages },
    { "getMemoryInfo",          "(ILandroid/os/Debug$MemoryInfo;)V",
            (void*) android_os_Debug_getDirtyPagesPid },
    { "getPss",                 "()J",
            (void*) android_os_Debug_getPss },
    { "getPss",                 "(I[J[J)J",
            (void*) android_os_Debug_getPssPid },
    { "getMemInfo",             "([J)V",
            (void*) android_os_Debug_getMemInfo },
    { "dumpNativeHeap",         "(Ljava/io/FileDescriptor;)V",
            (void*) android_os_Debug_dumpNativeHeap },
    { "dumpNativeMallocInfo",   "(Ljava/io/FileDescriptor;)V",
            (void*) android_os_Debug_dumpNativeMallocInfo },
    { "getBinderSentTransactions", "()I",
            (void*) android_os_Debug_getBinderSentTransactions },
    { "getBinderReceivedTransactions", "()I",
            (void*) android_os_getBinderReceivedTransactions },
    { "getBinderLocalObjectCount", "()I",
            (void*)android_os_Debug_getLocalObjectCount },
    { "getBinderProxyObjectCount", "()I",
            (void*)android_os_Debug_getProxyObjectCount },
    { "getBinderDeathObjectCount", "()I",
            (void*)android_os_Debug_getDeathObjectCount },
    { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
    { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
            (void*)android_os_Debug_getUnreachableMemory },
};
```

# まとめ

`dumpsys meminfo`は便利で複雑なのでついつい全情報を出力したくなるが、
内部的には`/proc/pid/smaps`の解析やGCの実行など、かなり重たい処理が実行される。
定期的に実行する場合はオプションを適切に使って出来る限り軽量にモニタするようにしたい。


# 参考
- [dumpsys - Android Developers](https://developer.android.com/studio/command-line/dumpsys)
- [RAM の使用状況を調査する - Android Developers](https://developer.android.com/studio/profile/investigate-ram.html)
- [/proc/pid/smaps_rollup](https://www.kernel.org/doc/Documentation/ABI/testing/procfs-smaps_rollup)
- [proc](https://www.kernel.org/doc/Documentation/filesystems/proc.txt)
- [man mallinfo(3)](http://man7.org/linux/man-pages/man3/mallinfo.3.html)
- [Androidソースコード検索サービス](https://sites.google.com/site/devcollaboration/codesearch)
- [android-8.1.0_r1.0](https://search.siprop.org/android-8.1.0_r1.0/)
- [Linux kernel android-4.4-o-release](https://android.googlesource.com/kernel/common/+/refs/heads/android-4.4-o-release)
- [KSM (KERNEL SAME-PAGE MERGING) - redhat](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/virtualization_tuning_and_optimization_guide/chap-ksm)
- [/proc/meminfoを考える - めもめも](http://enakai00.hatenablog.com/entry/20110906/1315315488)