一般Linux内核统计剩余内存都是meminfo的MemFree+Cached, maybe + Buffers, 如下:

xxxx:/ # cat /proc/meminfo
MemTotal: 1883144 kB
MemFree: 66536 kB
MemAvailable: 724464 kB
Buffers: 30180 kB
Cached: 678484 kB

不过Android原生系统里setting.apk界面剩余内存显示和内核有较大出入,来查下,代码是Android 7.0。

setting.apk里ProcessStatsSummary.java:

public void refreshUi() {
Context context = getContext();

MemInfo memInfo = mStatsManager.getMemInfo();

double usedRam = memInfo.realUsedRam;
double totalRam = memInfo.realTotalRam;
double freeRam = memInfo.realFreeRam;
BytesResult usedResult = Formatter.formatBytes(context.getResources(), (long) usedRam,Formatter.FLAG_SHORTER);
String totalString = Formatter.formatShortFileSize(context, (long) totalRam);
if(context.getResources().getBoolean(R.bool.config_support_show_system_volume)&&totalString.contains(".0")) {
totalString = totalString.replace(".0", "");
}
String freeString = Formatter.formatShortFileSize(context, (long) freeRam);
CharSequence memString;
CharSequence[] memStatesStr = getResources().getTextArray(R.array.ram_states);
int memState = mStatsManager.getMemState();
if (memState >= 0 && memState < memStatesStr.length - 1) {
memString = memStatesStr[memState];
} else {
memString = memStatesStr[memStatesStr.length - 1];
}
mSummaryPref.setAmount(usedResult.value);
mSummaryPref.setUnits(usedResult.units);
float usedRatio = (float)(usedRam / (freeRam + usedRam));
mSummaryPref.setRatios(usedRatio, 0, 1 - usedRatio);

mPerformance.setSummary(memString);
mTotalMemory.setSummary(totalString);
mAverageUsed.setSummary(Utils.formatPercentage((long) usedRam, (long) totalRam));
mFree.setSummary(freeString);
String durationString = getString(sDurationLabels[mDurationIndex]);
int numApps = mStatsManager.getEntries().size();
mAppListPreference.setSummary(getResources().getQuantityString(
R.plurals.memory_usage_apps_summary, numApps, numApps, durationString));
}
protected ProcStatsData mStatsManager;

ProcStatsData.java在packages/apps/Settings/src/com/android/settings/applications/ProcStatsData.java.

memInfo.realFreeRam是我们关心的

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 (DEBUG) {
Log.i(TAG, "Scaled Used RAM: " + Formatter.formatShortFileSize(context,
(long) realUsedRam));
Log.i(TAG, "Scaled Free RAM: " + Formatter.formatShortFileSize(context,
(long) realFreeRam));
}
if (DEBUG) {
Log.i(TAG, "Adj Scaled Used RAM: " + Formatter.formatShortFileSize(context,
(long) realUsedRam));
Log.i(TAG, "Adj Scaled Free RAM: " + Formatter.formatShortFileSize(context,
(long) realFreeRam));
}

ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(
memInfo);
if (memInfo.hiddenAppThreshold >= realFreeRam) {
realUsedRam = freeRam;
realFreeRam = 0;
baseCacheRam = (long) realFreeRam;
} else {
realUsedRam += memInfo.hiddenAppThreshold;
realFreeRam -= memInfo.hiddenAppThreshold; //去掉了cachedAppMem
baseCacheRam = memInfo.hiddenAppThreshold;
}
}

hiddenAppThreshold的赋值在frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java:

@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
outInfo.availMem = Process.getFreeMemory();
outInfo.totalMem = Process.getTotalMemory();
outInfo.threshold = homeAppMem;
outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
outInfo.hiddenAppThreshold = cachedAppMem; //在这里定义
outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
ProcessList.SERVICE_ADJ);
outInfo.visibleAppThreshold = mProcessList.getMemLevel(
ProcessList.VISIBLE_APP_ADJ);
outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
ProcessList.FOREGROUND_APP_ADJ);
}
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 906;
static final int CACHED_APP_MIN_ADJ = 900;
long getMemLevel(int adjustment) {
for (int i=0; i<mOomAdj.length; i++) {
if (adjustment <= mOomAdj[i]) {
return mOomMinFree[i] * 1024;
}
}
return mOomMinFree[mOomAdj.length-1] * 1024;
}
// This is a process currently hosting a backup operation.  Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 300;

// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 200;

// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 100;
static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1;

// This is the process running the current foreground app. We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;
...

// 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
};

mOomMinFree会根据mOomMinFreeLow和mOomMinFreeHigh通过一定计算方法得出,也就是lmk minfree参数倒数第二个了, cached min,注意单位是页.

来看看usedRam和freeRam是怎么来的:

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;

check totalMem

public void refreshStats(boolean forceLoad) {
if (mStats == null || forceLoad) {
load();
}

pkgEntries = new ArrayList<>();

long now = SystemClock.uptimeMillis();

memTotalTime = DumpUtils.dumpSingleTime(null, null, mStats.mMemFactorDurations,
mStats.mMemFactor, mStats.mStartTime, now);

ProcessStats.TotalMemoryUseCollection totalMem = new ProcessStats.TotalMemoryUseCollection(
ProcessStats.ALL_SCREEN_ADJ, mMemStates);
mStats.computeTotalMemoryUse(totalMem, now);

ok, lets check computeTotalMemoryUse

在frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java:

看下dumpTotalsLocked能看出来是根据pss计算得来

void dumpTotalsLocked(PrintWriter pw, long now) {
pw.println("Run time Stats:");
DumpUtils.dumpSingleTime(pw, " ", mMemFactorDurations, mMemFactor, mStartTime, now);
pw.println();
pw.println("Memory usage:");
TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
ALL_MEM_ADJ);
computeTotalMemoryUse(totalMem, now);
long totalPss = 0;
totalPss = printMemoryCategory(pw, " ", "Kernel ", totalMem.sysMemKernelWeight,
totalMem.totalTime, totalPss, totalMem.sysMemSamples);
totalPss = printMemoryCategory(pw, " ", "Native ", totalMem.sysMemNativeWeight,
totalMem.totalTime, totalPss, totalMem.sysMemSamples);
for (int i=0; i<STATE_COUNT; i++) {
// Skip restarting service state -- that is not actually a running process.
if (i != STATE_SERVICE_RESTARTING) {
totalPss = printMemoryCategory(pw, " ", DumpUtils.STATE_NAMES[i],
totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
totalMem.processStateSamples[i]);
}
}

ok, 然后再考虑某个时间段的使用确定内存使用统计。

对PSS的内核proc.txt有说明:

The “proportional set size” (PSS) of a process is the count of pages it has
in memory, where each page is divided by the number of processes sharing it.
So if a process has 1000 pages all to itself, and 1000 shared with one other
process, its PSS will be 1500.

/proc/PID/smaps里有Pss的统计, 统计代码可以看fs/proc/task_mmu.c

对于RSS, resident in RAM, lwn.net的历史说明就是应用没有共享页统计,所以要引入PSS。

The resident set size (RSS) number is a little better, but there is no information on sharing of pages there.

refer: https://lwn.net/Articles/230975/