メモリの空き状況
AndroidといってもLinuxなので、メモリを余らせるぐらいだったらキャッシュとして積極的に使う、という方針は同じ。
つまり、単純に未使用メモリサイズを見ても、システムとしての余力を知ることはできない。
そのため、Android Frameworkがいくつかメモリ空き状況を確認する方法を提供してくれている。
なお、参考にしたソースコードおよび動作確認環境はAndroid 8.1。
方法1.Developer Optionの「Memory Use」
スマホ単体で見られるので、一番お手軽。
長時間(デフォルト3時間)の平均値を返すので、大まかな傾向をつかむときに適している。
出力例
同等の値を出力するSettingsDumpService#dump
が実装されているので、dumpsys
コマンドで確認することも可能。
事前にSettingsDumpServiceを起動する必要がある。
出力はJSON。
$ 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
が空きメモリサイズとして表示される。
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
を引いている。
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を合計している。
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よりアグレッシブ。
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;
方法2. dumpsys meminfoのFree RAM
開発時にメモリ使用状況をモニタするという用途では、関連情報を含めて出力してくれるdumpsys meminfo
のほうがよく使われるかもしれない。
こちらは平均ではなくスナップショット。
出力例
$ adb shell dumpsys meminfo | grep "Free RAM"
Free RAM: 844,625K ( 163,029K cached pss + 356,620K cached kernel + 324,976K free)
算出方法
「いつでも殺していいプロセスのPSSの合計値」+「カーネルでキャッシュ用途に使われているメモリのうち、プロセスにマッピングされていないメモリの量」+「まだどの用途にも使われていないメモリの量」=「Androidとしていつでも回収可能なメモリの量」。
詳細はこちら。
ポイントとしては、ユーザ空間のプロセスが使用中のメモリも含めて、回収可能なメモリの量を算出しているところ。
方法3.ActivityManager.MemoryInfo#availMem
実行時にActivityManager.MemoryInfo#availMemを使う方法。
Android developerでも紹介されている。
出力例
残念ながらCUIで取得する方法は見つからなかったが、計算式が簡単なので/proc/meminfo
から取得可能。
$ 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にしなかったんだろう?
方法4.(番外編)/proc/meminfoのMemAvailable
Linuxとして見た場合、もっとも素直な方法。
ただし、もちろんAndroidの都合は知らないので、回収可能プロセスとかlowmemorykillerの閾値などは考慮しない。
$ adb shell grep '^MemAvailable:' /proc/meminfo
MemAvailable: 914492 kB
比較
1秒間隔で上記4つの方法でメモリ空き状況を計測した結果がこちら。
なお、エミュレータ上でyoutube動画をみたり、アプリを切り替えたり、割と操作しながら取得している。
# !/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
大まかな傾向は同じとしても、数割のオーダーで差があることがわかる。
まとめ
どの方法も一長一短あるので、一概にどの方法を使うべきとは言いにくいが、
混乱を避けるためにも計測・考察するときはきちんと計測方法に一貫性をもたせないといけない(当たり前)。
参考
CheckHowMuchMemory - Android developer
Developer Option - Android Developer
Documentation /proc
【RHEL】linuxメモリのfreeとmeminfoの関係を図解し利用率の計算方法を説明してみる
free(1)のtotalとかusedなどの各項目をカーネルの方から見てみる