我们查看应用内存都是通过adb shell dumpsys meminfo 应用名称或者pid 的方式获取
能获取的内容如下:
图1
数据项pss即是应用所占用的内存。那图中各项内容是怎么来的呢?
图2
图1种除了 EGL mtrack,GL mtrack都是从smaps文件种解析获得
EGL mtrack,GL mtrack这两项内容是通过service获取的代码如下
static int memtrack_proc_get_type(memtrack_proc_type *t,
pid_t pid, MemtrackType type)
{
int err = 0;
std::shared_ptr<IMemtrack> memtrack_proxy_service = get_memtrack_proxy_service();
if (!memtrack_proxy_service) {
return -1;
}
std::vector<MemtrackRecord> records;
auto status = memtrack_proxy_service->getMemory(pid, type, &records);
if (!status.isOk()) {
return -1;
}
t->records.resize(records.size());
for (size_t i = 0; i < records.size(); i++) {
t->records[i].sizeInBytes = records[i].sizeInBytes;
t->records[i].flags = records[i].flags;
}
return err;
}
EGL mtrack是SurfaceFlinger申请的surface大小
You will see this column when display driver’s memtrack module is enabled
Before Lollipop5.1, this column is named “Graphics”.
EGL memtrack memory is the summary of all surface buffers(the surface buffer increases to triple buffer after Android 4.1) and the size of the Atlas buffer.
However, Atlas buffer is actually a shared memory and shouldn’t be accounted into each UI process’ memory usage to overcount the memory usage.
Both surface buffer and Atlas buffer’s memory quota is reserved in project’s memory estimation, thus the memory usage of these buffers should be separately
accounted from process’ memory usage. So when you measure process’ memory usage, you can ignore this column.
GL mtrack 是gpu占用的内存
smaps解析可通过高爷的脚本查看GitHub - Gracker/Android-App-Memory-Analysis
这里可以简单看下Dalvik heap的内容:
Dalvik : 4.308 M
pss: 4.283 M
swapPss: 0.025 M
[anon:dalvik-zygote space] : 3252 kB //zygote进程的初始空间
[anon:dalvik-main space (region space)] : 920 kB //堆内存在这里分配,不设置largeSpace的话上限是256M
[anon:dalvik-free list large object space] : 116 kB // 大对象分配space
[anon:dalvik-non moving space] : 20 kB
Native : 31.422 M
pss: 31.308 M
swapPss: 0.114 M
[anon:libc_malloc] : 31422 kB //通过malloc接口申请的内存,图片内存分配之类的
脚本的原理可参考load_maps实现:
通过解析每行首字母来判断各行代表的内存分配的意义,
bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback,
bool read_smaps_fields) {
auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
if (fp == nullptr) {
return false;
}
char* line = nullptr;
bool parsing_vma = false;
ssize_t line_len;
size_t line_alloc = 0;
Vma vma;
while ((line_len = getline(&line, &line_alloc, fp.get())) > 0) {
// Make sure the line buffer terminates like a C string for ReadMapFile
line[line_len] = '\0';
if (parsing_vma) {
if (parse_smaps_field(line, &vma.usage)) {
// This was a stats field
continue;
}
// Done collecting stats, make the call back
callback(vma);
parsing_vma = false;
}
vma.clear();
// If it has, we are looking for the vma stats
// 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
//读取到新的一行
if (!::android::procinfo::ReadMapFileContent(
line, [&](const android::procinfo::MapInfo& mapinfo) {
vma.start = mapinfo.start;
vma.end = mapinfo.end;
vma.flags = mapinfo.flags;
vma.offset = mapinfo.pgoff;
vma.name = mapinfo.name;
vma.inode = mapinfo.inode;
vma.is_shared = mapinfo.shared;
})) {
// free getline() managed buffer
free(line);
LOG(ERROR) << "Failed to parse " << path;
return false;
}
if (read_smaps_fields) {
parsing_vma = true;
} else {
// Done collecting stats, make the call back
callback(vma);
}
}
// free getline() managed buffer
free(line);
if (parsing_vma) {
callback(vma);
}
return true;
}
static bool load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
*foundSwapPss = false;
uint64_t prev_end = 0;
int prev_heap = HEAP_UNKNOWN;
std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
auto vma_scan = [&](const meminfo::Vma& vma) {
int which_heap = HEAP_UNKNOWN;
int sub_heap = HEAP_UNKNOWN;
bool is_swappable = false;
std::string name;
if (base::EndsWith(vma.name, " (deleted)")) {
name = vma.name.substr(0, vma.name.size() - strlen(" (deleted)"));
} else {
name = vma.name;
}
uint32_t namesz = name.size();
if (base::StartsWith(name, "[heap]")) {
which_heap = HEAP_NATIVE;
} else if (base::StartsWith(name, "[anon:libc_malloc]")) {
which_heap = HEAP_NATIVE;
} else if (base::StartsWith(name, "[anon:scudo:")) {
which_heap = HEAP_NATIVE;
} else if (base::StartsWith(name, "[anon:GWP-ASan")) {
which_heap = HEAP_NATIVE;
} else if (base::StartsWith(name, "[stack")) {
which_heap = HEAP_STACK;
} else if (base::StartsWith(name, "[anon:stack_and_tls:")) {
which_heap = HEAP_STACK;
} else if (base::EndsWith(name, ".so")) {
which_heap = HEAP_SO;
is_swappable = true;
} else if (base::EndsWith(name, ".jar")) {
which_heap = HEAP_JAR;
is_swappable = true;
} else if (base::EndsWith(name, ".apk")) {
which_heap = HEAP_APK;
is_swappable = true;
} else if (base::EndsWith(name, ".ttf")) {
which_heap = HEAP_TTF;
is_swappable = true;
} else if ((base::EndsWith(name, ".odex")) ||
(namesz > 4 && strstr(name.c_str(), ".dex") != nullptr)) {
which_heap = HEAP_DEX;
sub_heap = HEAP_DEX_APP_DEX;
is_swappable = true;
} else if (base::EndsWith(name, ".vdex")) {
which_heap = HEAP_DEX;
// Handle system@framework@boot and system/framework/boot|apex
if ((strstr(name.c_str(), "@boot") != nullptr) ||
(strstr(name.c_str(), "/boot") != nullptr) ||
(strstr(name.c_str(), "/apex") != nullptr)) {
sub_heap = HEAP_DEX_BOOT_VDEX;
} else {
sub_heap = HEAP_DEX_APP_VDEX;
}
is_swappable = true;
} else if (base::EndsWith(name, ".oat")) {
which_heap = HEAP_OAT;
is_swappable = true;
} else if (base::EndsWith(name, ".art") || base::EndsWith(name, ".art]")) {
which_heap = HEAP_ART;
// Handle system@framework@boot* and system/framework/boot|apex*
if ((strstr(name.c_str(), "@boot") != nullptr) ||
(strstr(name.c_str(), "/boot") != nullptr) ||
(strstr(name.c_str(), "/apex") != nullptr)) {
sub_heap = HEAP_ART_BOOT;
} else {
sub_heap = HEAP_ART_APP;
}
is_swappable = true;
} else if (base::StartsWith(name, "/dev/")) {
which_heap = HEAP_UNKNOWN_DEV;
if (base::StartsWith(name, "/dev/kgsl-3d0")) {
which_heap = HEAP_GL_DEV;
} else if (base::StartsWith(name, "/dev/ashmem/CursorWindow")) {
which_heap = HEAP_CURSOR;
} else if (base::StartsWith(name, "/dev/ashmem/jit-zygote-cache")) {
which_heap = HEAP_DALVIK_OTHER;
sub_heap = HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE;
} else if (base::StartsWith(name, "/dev/ashmem")) {
which_heap = HEAP_ASHMEM;
}
} else if (base::StartsWith(name, "/memfd:jit-cache")) {
which_heap = HEAP_DALVIK_OTHER;
sub_heap = HEAP_DALVIK_OTHER_APP_CODE_CACHE;
} else if (base::StartsWith(name, "/memfd:jit-zygote-cache")) {
which_heap = HEAP_DALVIK_OTHER;
sub_heap = HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE;
} else if (base::StartsWith(name, "[anon:")) {
which_heap = HEAP_UNKNOWN;
if (base::StartsWith(name, "[anon:dalvik-")) {
which_heap = HEAP_DALVIK_OTHER;
if (base::StartsWith(name, "[anon:dalvik-LinearAlloc")) {
sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC;
} else if (base::StartsWith(name, "[anon:dalvik-alloc space") ||
base::StartsWith(name, "[anon:dalvik-main space")) {
// This is the regular Dalvik heap.
which_heap = HEAP_DALVIK;
sub_heap = HEAP_DALVIK_NORMAL;
} else if (base::StartsWith(name,
"[anon:dalvik-large object space") ||
base::StartsWith(
name, "[anon:dalvik-free list large object space")) {
which_heap = HEAP_DALVIK;
sub_heap = HEAP_DALVIK_LARGE;
} else if (base::StartsWith(name, "[anon:dalvik-non moving space")) {
which_heap = HEAP_DALVIK;
sub_heap = HEAP_DALVIK_NON_MOVING;
} else if (base::StartsWith(name, "[anon:dalvik-zygote space")) {
which_heap = HEAP_DALVIK;
sub_heap = HEAP_DALVIK_ZYGOTE;
} else if (base::StartsWith(name, "[anon:dalvik-indirect ref")) {
sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
} else if (base::StartsWith(name, "[anon:dalvik-jit-code-cache") ||
base::StartsWith(name, "[anon:dalvik-data-code-cache")) {
sub_heap = HEAP_DALVIK_OTHER_APP_CODE_CACHE;
} else if (base::StartsWith(name, "[anon:dalvik-CompilerMetadata")) {
sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
} else {
sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting.
}
}
} else if (namesz > 0) {
which_heap = HEAP_UNKNOWN_MAP;
} else if (vma.start == prev_end && prev_heap == HEAP_SO) {
// bss section of a shared library
which_heap = HEAP_SO;
}
prev_end = vma.end;
prev_heap = which_heap;
const meminfo::MemUsage& usage = vma.usage;
if (usage.swap_pss > 0 && *foundSwapPss != true) {
*foundSwapPss = true;
}
uint64_t swapable_pss = 0;
if (is_swappable && (usage.pss > 0)) {
float sharing_proportion = 0.0;
if ((usage.shared_clean > 0) || (usage.shared_dirty > 0)) {
sharing_proportion = (usage.pss - usage.uss) / (usage.shared_clean + usage.shared_dirty);
}
swapable_pss = (sharing_proportion * usage.shared_clean) + usage.private_clean;
}
stats[which_heap].pss += usage.pss;
stats[which_heap].swappablePss += swapable_pss;
stats[which_heap].rss += usage.rss;
stats[which_heap].privateDirty += usage.private_dirty;
stats[which_heap].sharedDirty += usage.shared_dirty;
stats[which_heap].privateClean += usage.private_clean;
stats[which_heap].sharedClean += usage.shared_clean;
stats[which_heap].swappedOut += usage.swap;
stats[which_heap].swappedOutPss += usage.swap_pss;
if (which_heap == HEAP_DALVIK || which_heap == HEAP_DALVIK_OTHER ||
which_heap == HEAP_DEX || which_heap == HEAP_ART) {
stats[sub_heap].pss += usage.pss;
stats[sub_heap].swappablePss += swapable_pss;
stats[sub_heap].rss += usage.rss;
stats[sub_heap].privateDirty += usage.private_dirty;
stats[sub_heap].sharedDirty += usage.shared_dirty;
stats[sub_heap].privateClean += usage.private_clean;
stats[sub_heap].sharedClean += usage.shared_clean;
stats[sub_heap].swappedOut += usage.swap;
stats[sub_heap].swappedOutPss += usage.swap_pss;
}
};
return meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
}