[Android] システム全体のメモリ空き状況を知る方法

# メモリの空き状況
AndroidといってもLinuxなので、メモリを余らせるぐらいだったらキャッシュとして積極的に使う、という方針は同じ。
つまり、単純に未使用メモリサイズを見ても、システムとしての余力を知ることはできない。
そのため、Android Frameworkがいくつかメモリ空き状況を確認する方法を提供してくれている。

なお、参考にしたソースコードおよび動作確認環境はAndroid 8.1。

# 方法１．[Developer Option](https://developer.android.com/studio/debug/dev-options?hl=JA)の「Memory Use」

スマホ単体で見られるので、一番お手軽。
長時間(デフォルト3時間)の平均値を返すので、大まかな傾向をつかむときに適している。

## 出力例

![developer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/a6d6b61f-5a44-baea-1d75-f9cc0b2a5ff7.png)
![memory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/c0b4347b-6b0c-70a2-569b-16d87ffec63c.png)

同等の値を出力する`SettingsDumpService#dump`が実装されているので、`dumpsys`コマンドで確認することも可能。
事前にSettingsDumpServiceを起動する必要がある。
出力はJSON。


```shell-session
$ adb shell am start-service com.android.settings/.SettingsDumpService dump
Starting service: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.settings/.SettingsDumpService }

$ adb shell dumpsys activity service com.android.settings/.SettingsDumpService 
SERVICE com.android.settings/.SettingsDumpService fe51c54 pid=7867
  Client:
    {"service":"Settings State","storage":{"private":{"used":"185585664","total":"812531712","state":2,"stateDesc":17039942,"description":"Internal shared storage"},"public:253,80":{"used":"63488","total":"835573760","path":"\/mnt\/media_rw\/16F1-1D13","state":2,"stateDesc":17039942,"description":"SDCARD"},"emulated":{"used":"185585664","total":"812531712","path":"\/data\/media","state":2,"stateDesc":17039942,"description":"Internal shared storage"}},"datausage":{"cell":[{"start":1569164400000,"usage":30348283,"warning":2147483648,"limit":0,"subId":1}],"wifi":{"start":1567004932178,"usage":9445671,"warning":2147483648,"limit":0}},"memory":{"used":"1.1030173676369073E9","free":"9.882568723630927E8","total":"2.09127424E9","state":0},"default_browser_app":"com.android.chrome","anomaly_detection":{"anomaly_config_version":"0"}}

$ adb shell dumpsys activity service com.android.settings/.SettingsDumpService | tail -1 | jq '.memory'
{
  "used": "1.1418785536824732E9",
  "free": "9.493956863175266E8",
  "total": "2.09127424E9",
  "state": 0
}
```

## 算出方法

ざっくり言えば、Cached + MemFree + 回収可能プロセスのPSS - hiddenAppThreshold。
ということで、lowmemorykiller発動までの余裕を求めようとしているっぽい。

Memory Usageの値の設定は、`ProcessStatsSummary.refreshUI`で行っている。
`ProcStatsData.MemInfo#realFreeRam`が空きメモリサイズとして表示される。

```java:packages/apps/Settings/src/com/android/settings/applications/ProcessStatsSummary.java
    public void refreshUi() {
        Context context = getContext();

        MemInfo memInfo = mStatsManager.getMemInfo();

        double usedRam = memInfo.realUsedRam;
        double totalRam = memInfo.realTotalRam;
        double freeRam = memInfo.realFreeRam;
```

`MemInfo`では、`calculateWeightInfo`を呼び出したのち、`hiddenAppThreshold`を引いている。

```java:packages/apps/Settings/src/com/android/settings/applications/ProcStatsData.java
private MemInfo(Context context, ProcessStats.TotalMemoryUseCollection totalMem,
                long memTotalTime) {
            this.memTotalTime = memTotalTime;
            calculateWeightInfo(context, totalMem, memTotalTime);

            double usedRam = (usedWeight * 1024) / memTotalTime;
            double freeRam = (freeWeight * 1024) / memTotalTime;
            totalRam = usedRam + freeRam;
            totalScale = realTotalRam / totalRam;
            weightToRam = totalScale / memTotalTime * 1024;

            realUsedRam = usedRam * totalScale;
            realFreeRam = freeRam * totalScale;

...

            if (memInfo.hiddenAppThreshold >= realFreeRam) {
                realUsedRam = freeRam;
                realFreeRam = 0;
                baseCacheRam = (long) realFreeRam;
            } else {
                realUsedRam += memInfo.hiddenAppThreshold;
                realFreeRam -= memInfo.hiddenAppThreshold;
                baseCacheRam = memInfo.hiddenAppThreshold;
            }
       }
```

`ProcessStats.computeTotalMemoryUse`
で取得したMemFreeとCached、そして回収可能プロセスのPSSを合計している。

```java:packages/apps/Settings/src/com/android/settings/applications/ProcStatsData.java
private void calculateWeightInfo(Context context, TotalMemoryUseCollection totalMem,
                long memTotalTime) {
            MemInfoReader memReader = new MemInfoReader();
            memReader.readMemInfo();
            realTotalRam = memReader.getTotalSize();
            freeWeight = totalMem.sysMemFreeWeight + totalMem.sysMemCachedWeight;

...

            for (int i = 0; i < ProcessStats.STATE_COUNT; i++) {
                if (i == ProcessStats.STATE_SERVICE_RESTARTING) {
                    // These don't really run.
                    mMemStateWeights[i] = 0;
                } else {
                    mMemStateWeights[i] = totalMem.processStateWeight[i];
                    if (i >= ProcessStats.STATE_HOME) {
                        freeWeight += totalMem.processStateWeight[i];
                    } else {
                        usedWeight += totalMem.processStateWeight[i];
                    }
                }
            }
```

回収可能プロセスとされるのは以下のとおり。
HOME, LASTも含まれるぶん、方法2よりアグレッシブ。

```java:frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
    public static final int STATE_HOME = 9;
    public static final int STATE_LAST_ACTIVITY = 10;
    public static final int STATE_CACHED_ACTIVITY = 11;
    public static final int STATE_CACHED_ACTIVITY_CLIENT = 12;
    public static final int STATE_CACHED_EMPTY = 13;
```

# 方法２. dumpsys meminfoのFree RAM

開発時にメモリ使用状況をモニタするという用途では、関連情報を含めて出力してくれる`dumpsys meminfo`のほうがよく使われるかもしれない。
こちらは平均ではなくスナップショット。

## 出力例

```shell-session
$ adb shell dumpsys meminfo | grep "Free RAM"
 Free RAM:   844,625K (  163,029K cached pss +   356,620K cached kernel +   324,976K free)
```

## 算出方法

「いつでも殺していいプロセスのPSSの合計値」＋「カーネルでキャッシュ用途に使われているメモリのうち、プロセスにマッピングされていないメモリの量」＋「まだどの用途にも使われていないメモリの量」＝「Androidとしていつでも回収可能なメモリの量」。

詳細は[こちら](https://qiita.com/takeoverjp/items/0fe909d3518394568698#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%85%A8%E4%BD%93%E3%81%AE%E3%83%A1%E3%83%A2%E3%83%AA%E4%BD%BF%E7%94%A8%E7%8A%B6%E6%B3%81)。

ポイントとしては、ユーザ空間のプロセスが使用中のメモリも含めて、回収可能なメモリの量を算出しているところ。

# 方法３．[ActivityManager.MemoryInfo#availMem](https://developer.android.com/reference/android/app/ActivityManager.MemoryInfo.html#availMem)

実行時に[ActivityManager.MemoryInfo#availMem](https://developer.android.com/reference/android/app/ActivityManager.MemoryInfo.html#availMem)を使う方法。

[Android developer](https://developer.android.com/topic/performance/memory#CheckHowMuchMemory)でも紹介されている。

## 出力例

残念ながらCUIで取得する方法は見つからなかったが、計算式が簡単なので`/proc/meminfo`から取得可能。

```shell-session
$ adb shell grep -e '^MemFree:' -e '^Cached:' /proc/meminfo | awk '{SUM+=$2} END {print "availMem: " SUM " kB"}'
availMem: 1068348 kB
```

## 算出方法

`/proc/meminfo`のMemFree + Cached。
各アプリから頻繁に呼び出されることを想定したのか、とてもシンプル。
回収可能可能プロセスのことは考慮しない。
でもそれならなんでMemAvailableにしなかったんだろう？

# 方法４．(番外編)/proc/meminfoのMemAvailable

Linuxとして見た場合、もっとも素直な方法。
ただし、もちろんAndroidの都合は知らないので、回収可能プロセスとかlowmemorykillerの閾値などは考慮しない。

```
$ adb shell grep '^MemAvailable:' /proc/meminfo
MemAvailable:     914492 kB
```

# 比較
１秒間隔で上記４つの方法でメモリ空き状況を計測した結果がこちら。
なお、エミュレータ上でyoutube動画をみたり、アプリを切り替えたり、割と操作しながら取得している。

```shell
#!/bin/sh -e

echo DEVELOPER,DUMPSYS,AM,PROC
while true; do
    DEVELOPER=$(printf "%.0f\n" $(adb shell dumpsys activity service com.android.settings/.SettingsDumpService  | tail -1 | jq -r '.memory.free'))
    DUMPSYS=$(adb shell dumpsys meminfo -c | grep '^ram,' | cut -d',' -f3 | xargs -I@ expr @ \* 1024)
    AM=$(adb shell grep -e '^MemFree:' -e '^Cached:' /proc/meminfo | awk '{SUM+=$2} END {print SUM * 1024}')
    PROC=$(adb shell grep MemAvailable /proc/meminfo | awk '{SUM+=$2} END {print SUM * 1024}')
    echo ${DEVELOPER},${DUMPSYS},${AM},${PROC}
    sleep 1
done
```

![remaining.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/320673ce-e552-f07e-e5d9-6151ddcfa189.png)

大まかな傾向は同じとしても、数割のオーダーで差があることがわかる。



# まとめ
どの方法も一長一短あるので、一概にどの方法を使うべきとは言いにくいが、
混乱を避けるためにも計測・考察するときはきちんと計測方法に一貫性をもたせないといけない（当たり前）。

# 参考
[CheckHowMuchMemory - Android developer](https://developer.android.com/topic/performance/memory#CheckHowMuchMemory)
[Developer Option - Android Developer](https://developer.android.com/studio/debug/dev-options)
[Documentation /proc](https://raw.githubusercontent.com/torvalds/linux/master/Documentation/filesystems/proc.txt)
[【RHEL】linuxメモリのfreeとmeminfoの関係を図解し利用率の計算方法を説明してみる](http://nopipi.hatenablog.com/entry/2015/09/13/181026?amp=1)
[free(1)のtotalとかusedなどの各項目をカーネルの方から見てみる](https://kernhack.hatenablog.com/entry/2016/05/05/011814)