一. 获取系统可用内存的原理
linux 系统中 /proc/meminfo 这个文件用来记录当前系统内存使用的详细情况。通过读取该文件即可知道当前内存的使用情况。
/proc 文件系统是存储与内存而不是硬盘,/proc 虚拟文件系统实质是以文件系统的形式访问内核数据的接口。
该文件的格式如下:
$cat /proc/meminfo
MemTotal: 8052444 kB
MemFree: 2754588 kB
MemAvailable: 3934252 kB
Buffers: 137128 kB
Cached: 1948128 kB
SwapCached: 0 kB
Active: 3650920 kB
Inactive: 1343420 kB
Active(anon): 2913304 kB
Inactive(anon): 727808 kB
Active(file): 737616 kB
Inactive(file): 615612 kB
Unevictable: 196 kB
Mlocked: 196 kB
SwapTotal: 8265724 kB
SwapFree: 8265724 kB
Dirty: 104 kB
Writeback: 0 kB
AnonPages: 2909332 kB
Mapped: 815524 kB
Shmem: 732032 kB
Slab: 153096 kB
SReclaimable: 99684 kB
SUnreclaim: 53412 kB
KernelStack: 14288 kB
PageTables: 62192 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 12291944 kB
Committed_AS: 11398920 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
HardwareCorrupted: 0 kB
AnonHugePages: 1380352 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 201472 kB
DirectMap2M: 5967872 kB
DirectMap1G: 3145728 kB
二. 获取系统内存信息的几种方式
1. Runtime
val r = Runtime.getRuntime()
Log.i(
"Dixon_Test",
"Memory Info:Total ${r.totalMemory() / 1024 / 1024} MB. Available ${r.maxMemory() / 1024 / 1024} MB."
)
Runtime 是 Java 的类,它获取的是当前 jvm heap 的内存信息。
maxMemory()
是当前 jvm heap 可分配的最大内存。
totalMemory()
是当前 jvm heap 可使用的最大内存。
以上均不是获取 系统 可用内存的方法。
2. Android API
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mi = ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
Log.i(
"Dixon_Test",
"Memory Info:Total ${mi.totalMem / 1024 / 1024} MB. Available ${mi.availMem / 1024 / 1024} MB."
)
其中,totalMem
为可供系统支配的内存,它抛去了 BIOS、内核保留的内存。availMem
为系统可用内存数大小。
3. 读取 /proc/meminfo
// 仅做原理演示 网上有更好的封装
FILE *file = fopen("/proc/meminfo", "r"); // storage 和 proc 在同一根目录下
if (file == nullptr) {
LOGI("Memory file could not be found");
}
char totKb[20];
char avaKb[20];
char freeKb[20];
fscanf(file, "MemTotal: %s kB\n", totKb); // 读取匹配的一行并解析为变量
long total_mem = stol(totKb) / 1024;
fscanf(file, "MemFree: %s kB\n", freeKb);
long free_mem = stol(freeKb) / 1024;
fscanf(file, "MemAvailable: %s kB\n", avaKb);
long available_mem = stol(avaKb) / 1024;
LOGI("Memory Info:Total %ld MB. Free %ld MB. Available %ld MB.\n",
total_mem, free_mem, available_mem);
fclose(file);
通过读取 /proc/meminfo 也能获取 MemAvailable,但是它真的等同于 MemoryInfo.availMem 吗?
三. 测试与分析
1. 测试
这里我测试了"使用系统 API "和"读取 /proc/meminfo 文件"俩种获取可用内存方式的区别:
// 读取 /proc/meminfo 的 jni 方法,详细代码见 2-3
NativeCall().getMemoryInfo()
// 系统 API 获取
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mi = ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
Log.i(
"Dixon_Test",
"Memory Info:Total ${mi.totalMem / 1024 / 1024} MB. Available ${mi.availMem / 1024 / 1024} MB."
)
在我的测试机上,输出结果如下:
I/Dixon_Test: Memory Info:Total 7473 MB. Free 231 MB. Available 4195 MB.
I/Dixon_Test: Memory Info:Total 7473 MB. Available 3489 MB.
发现俩者竟然不同,这是为什么呢?
2. 源码分析
这里我决定通过查看 am.getMemoryInfo(mi)
源码来探究原因,它的源码如下:
public void getMemoryInfo(MemoryInfo outInfo) {
try {
getService().getMemoryInfo(outInfo);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
getService()
跨进程返回了 IActivityManager
服务,如果你熟悉 Android 源码的命名规则,就知道它的实际类型是 ActivityManagerService
。
所以 getService().getMemoryInfo
实际调用了 ActivityManagerService.getMemoryInfo()
:
@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
mProcessList.getMemoryInfo(outInfo);
}
继续往下看 mProcessList.getMemoryInfo
:
void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
final long homeAppMem = getMemLevel(HOME_APP_ADJ);
final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ);
outInfo.availMem = getFreeMemory(); // 1
outInfo.totalMem = getTotalMemory();
outInfo.threshold = homeAppMem;
outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
outInfo.hiddenAppThreshold = cachedAppMem;
outInfo.secondaryServerThreshold = getMemLevel(SERVICE_ADJ);
outInfo.visibleAppThreshold = getMemLevel(VISIBLE_APP_ADJ);
outInfo.foregroundAppThreshold = getMemLevel(FOREGROUND_APP_ADJ);
}
代码注释 1 处,availMem 通过 getFreeMemory()
获得了赋值,看下它的实现:
// frameworks/base/core/java/android/os/Process.java
public static final native long getFreeMemory();
Process 调用了 Native 实现,而 Process 对应的 jni 实现为 frameworks/base/core/jni/android_util_Process.cpp
。
这里源码类的命名同样是有规律的。
static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz)
{
std::array<std::string_view, 2> memFreeTags = {
::android::meminfo::SysMemInfo::kMemFree,
::android::meminfo::SysMemInfo::kMemCached,
}; // 1.
std::vector<uint64_t> mem(memFreeTags.size());
::android::meminfo::SysMemInfo smi;
if (!smi.ReadMemInfo(memFreeTags.size(),
memFreeTags.data(),
mem.data())) {
jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory");
return -1L;
}
jlong sum = 0;
std::for_each(mem.begin(), mem.end(), [&](uint64_t val) { sum += val; });
return sum * 1024;
}
注释 1 处似曾相识,通过全局查找:
// /system/memory/libmeminfo/include/meminfo/sysmeminfo.h
static constexpr const char kMemFree[] = "MemFree:";
static constexpr const char kMemBuffers[] = "Buffers:";
static constexpr const char kMemCached[] = "Cached:";
至此基本可以推断处, Android 系统 API 返回的 Available 其实由 MemFree、Cached 俩部分构成。
既然如此,我们就修改读取 /proc/meminfo 的代码来验证一下:
FILE *file = fopen("/proc/meminfo", "r"); // storage 和 proc 在同一根目录下
if (file == nullptr) {
LOGI("Memory file could not be found");
}
char totKb[20];
char freeKb[20];
char cached[20];
fscanf(file, "MemTotal: %s kB\n", totKb); // 读取一行并解析为变量
long total_mem = stol(totKb) / 1024L;
fscanf(file, "MemFree: %s kB\n", freeKb);
long free_mem = stol(freeKb) / 1024L;
fscanf(file, "MemAvailable: %s kB\n", cached);
fscanf(file, "Buffers: %s kB\n", cached);
fscanf(file, "Cached: %s kB\n", cached);
long cache_mem = stol(cached) / 1024L;
long real_ava_mem = free_mem + cache_mem;
LOGI("Memory Info:Total %ld MB. Free %ld MB. Available %ld MB.\n",
total_mem, free_mem, real_ava_mem);
fclose(file);
测试结果如下:
I/Dixon_Test: Memory Info:Total 7473 MB. Free 171 MB. Available 3563 MB.
I/Dixon_Test: Memory Info:Total 7473 MB. Available 3564 MB.
验证通过,MemoryInfo.availMem
= MemFree
+ Cached
。
三. 分析总结
简单的讲,Linux 的内存可以这样划分:
系统内存 = 空闲内存 + 内核内存 + 用户内存
这其中,内核内存、用户内存均存在可回收部分,因此 /proc/meminfo 文件的 MemAvailable 指的是空闲内存(MemFree) + 内核内存的可回收部分 + 用户内存的可回收部分(有更复杂的计算公式但不在本文讨论之列)。
而 API MemoryInfo.availMem 是 MemFree 和 Cached 的总和,Cached 可以理解为读写文件时,Linux 内核为了提高读写性能与速度,将文件在内存中进行的缓存。
由于 Cached 是用户内存的一部分,因此 MemoryInfo.availMem 总是小于 /proc/meminfo 文件的 MemAvailable。
至于为什么有这种区别,我的理解是:
/proc/meminfo 文件的 MemAvailable 是 Linux 系统的可用内存,MemoryInfo.availMem 是 Android 的 API,是给建立在 JVM 虚拟机之上的 Android 应用使用的。前者在 Linux 内核层,后者在应用层,俩者对于可用内存的定义是不同的,正如应用层不能控制内核的内存进行回收,因此返回内核可回收的内存毫无意义,所以应用层返回的是 MemFree 和 Cached。
个人理解,欢迎评论区讨论。