美文网首页
Android 内存信息源码解析

Android 内存信息源码解析

作者: 小阿牛的爸爸 | 来源:发表于2021-10-27 17:00 被阅读0次

最近发现AMS提供了一个获取进程内存信息的接口,自己刚好要用到这一块,就抽空分析了一下。

1 入口是ActivityManagerService.java 的 getProcessMemoryInfo

@Override
public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) {
    // 1. 权限校验
    enforceNotIsolatedCaller("getProcessMemoryInfo");
   // 2. 创建返回结果MemoryInfo[]
    Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length];
    for (int i=pids.length-1; i>=0; i--) {
        // 3. 获取ProcessRecord,等会儿要把获取到的内存信息保存进去。之所以先获取的目的是保证这个进程是一个稳定的进程。要不然刚创建就删除就没有保存的必要了
        ProcessRecord proc;
        int oomAdj;
        synchronized (this) {
            synchronized (mPidsSelfLocked) {
                proc = mPidsSelfLocked.get(pids[i]);
                oomAdj = proc != null ? proc.setAdj : 0;
            }
        }
        infos[i] = new Debug.MemoryInfo();
        long startTime = SystemClock.currentThreadTimeMillis();
        // 4. 去Debug类获取内存信息
        Debug.getMemoryInfo(pids[i], infos[i]);
        long endTime = SystemClock.currentThreadTimeMillis();
       // 5. 如果进程稳定的话,把内存信息记录下来
        if (proc != null) {
            synchronized (this) {
                if (proc.thread != null && proc.setAdj == oomAdj) {
                    // Record this for posterity if the process has been stable.
                    proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
                            infos[i].getTotalUss(), infos[i].getTotalRss(), false,
                            ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime-startTime,
                            proc.pkgList);
                }
            }
        }
    }
    return infos;
}

从上面代码分析,可以发现AMS主要做了这么几件事:

  1. 检验调用方权限
  2. 去Debug类中获取内存信息
  3. 把获取到的信息保存到ProceeRecord
    所以,获取内存信息的主要处理是在Debug中,我们接着向下分析。

2. Debug.java 的 getMemoryInfo

这是一个Native方法,这里只有通过jni找到对应的c++类来分析了。

public static native void getMemoryInfo(int pid, MemoryInfo memoryInfo)

3. android_os_Debug.cpp 的 static const JNINativeMethod gMethods[]

通过搜索jni的实现发现,getMemoryInfo的jni定义在android_os_Debug.cpp中,其对应的c++方法是(void*) android_os_Debug_getDirtyPagesPid

static const JNINativeMethod gMethods[] = {
    { "getNativeHeapSize",      "()J",
            (void*) android_os_Debug_getNativeHeapSize },
    { "getNativeHeapAllocatedSize", "()J",
            (void*) android_os_Debug_getNativeHeapAllocatedSize },
    { "getNativeHeapFreeSize",  "()J",
            (void*) android_os_Debug_getNativeHeapFreeSize },
    { "getMemoryInfo",          "(Landroid/os/Debug$MemoryInfo;)V",
            (void*) android_os_Debug_getDirtyPages },
    { "getMemoryInfo",          "(ILandroid/os/Debug$MemoryInfo;)V",
            (void*) android_os_Debug_getDirtyPagesPid },
    { "getPss",                 "()J",
            (void*) android_os_Debug_getPss },
    { "getPss",                 "(I[J[J)J",
            (void*) android_os_Debug_getPssPid },
    { "getMemInfo",             "([J)V",
            (void*) android_os_Debug_getMemInfo },
    { "dumpNativeHeap",         "(Ljava/io/FileDescriptor;)V",
            (void*) android_os_Debug_dumpNativeHeap },
    { "dumpNativeMallocInfo",   "(Ljava/io/FileDescriptor;)V",
            (void*) android_os_Debug_dumpNativeMallocInfo },
    { "getBinderSentTransactions", "()I",
            (void*) android_os_Debug_getBinderSentTransactions },
    { "getBinderReceivedTransactions", "()I",
            (void*) android_os_getBinderReceivedTransactions },
    { "getBinderLocalObjectCount", "()I",
            (void*)android_os_Debug_getLocalObjectCount },
    { "getBinderProxyObjectCount", "()I",
            (void*)android_os_Debug_getProxyObjectCount },
    { "getBinderDeathObjectCount", "()I",
            (void*)android_os_Debug_getDeathObjectCount },
    { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
    { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
            (void*)android_os_Debug_getUnreachableMemory },
};

4. android_os_Debug.cpp的android_os_Debug_getDirtyPagesPid

static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
        jint pid, jobject object)
{
    bool foundSwapPss;
    stats_t stats[_NUM_HEAP];
    memset(&stats, 0, sizeof(stats));

    // 1. 从linux /proc/{pid}/smaps 读取信息到stats数组中,后面会详细分析该函数
    load_maps(pid, stats, &foundSwapPss);

    struct graphics_memory_pss graphics_mem;
    // 2. 读取图形占用的内存,保存到stats中
    if (read_memtrack_memory(pid, &graphics_mem) == 0) {
        stats[HEAP_GRAPHICS].pss = graphics_mem.graphics;
        stats[HEAP_GRAPHICS].privateDirty = graphics_mem.graphics;
        stats[HEAP_GRAPHICS].rss = graphics_mem.graphics;
        stats[HEAP_GL].pss = graphics_mem.gl;
        stats[HEAP_GL].privateDirty = graphics_mem.gl;
        stats[HEAP_GL].rss = graphics_mem.gl;
        stats[HEAP_OTHER_MEMTRACK].pss = graphics_mem.other;
        stats[HEAP_OTHER_MEMTRACK].privateDirty = graphics_mem.other;
        stats[HEAP_OTHER_MEMTRACK].rss = graphics_mem.other;
    }
    
   // 3. 超过_NUM_CORE_HEAP之后的内存段信息统一归属到UNKNOWN中
    for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) {
        stats[HEAP_UNKNOWN].pss += stats[i].pss;
        stats[HEAP_UNKNOWN].swappablePss += stats[i].swappablePss;
        stats[HEAP_UNKNOWN].rss += stats[i].rss;
        stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty;
        stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty;
        stats[HEAP_UNKNOWN].privateClean += stats[i].privateClean;
        stats[HEAP_UNKNOWN].sharedClean += stats[i].sharedClean;
        stats[HEAP_UNKNOWN].swappedOut += stats[i].swappedOut;
        stats[HEAP_UNKNOWN].swappedOutPss += stats[i].swappedOutPss;
    }

   // 5. 把上述信息中保存到jni中,返回给java层
    for (int i=0; i<_NUM_CORE_HEAP; i++) {
        env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss);
        env->SetIntField(object, stat_fields[i].pssSwappable_field, stats[i].swappablePss);
        env->SetIntField(object, stat_fields[i].rss_field, stats[i].rss);
        env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty);
        env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty);
        env->SetIntField(object, stat_fields[i].privateClean_field, stats[i].privateClean);
        env->SetIntField(object, stat_fields[i].sharedClean_field, stats[i].sharedClean);
        env->SetIntField(object, stat_fields[i].swappedOut_field, stats[i].swappedOut);
        env->SetIntField(object, stat_fields[i].swappedOutPss_field, stats[i].swappedOutPss);
    }

   // 6. 设置是否含有foundSwapPss标志
    env->SetBooleanField(object, hasSwappedOutPss_field, foundSwapPss);
    // 7. 查看MemeryInfo中是否含有otherStats字段,如果有的话,把Unknow那部分内存赋值到该字段中
    jintArray otherIntArray = (jintArray)env->GetObjectField(object, otherStats_field);

    jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0);
    if (otherArray == NULL) {
        return;
    }

    int j=0;
    for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) {
        otherArray[j++] = stats[i].pss;
        otherArray[j++] = stats[i].swappablePss;
        otherArray[j++] = stats[i].rss;
        otherArray[j++] = stats[i].privateDirty;
        otherArray[j++] = stats[i].sharedDirty;
        otherArray[j++] = stats[i].privateClean;
        otherArray[j++] = stats[i].sharedClean;
        otherArray[j++] = stats[i].swappedOut;
        otherArray[j++] = stats[i].swappedOutPss;
    }
   // 8. 内存清理
    env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);
}

总结一下,上面代码主要做了一下几件事情:

  1. 从linux的/proc/{pid}/smaps读取进程的内存信息,然后保存到返回数组stats_t
  2. 读取图形占用的内存信息,也保存到stats_t中
  3. 把核心堆外的其他内存保存到Unknow和otherStats数组中

4.1 load_maps解析

static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
    *foundSwapPss = false;

    // 1. 组装文件路径 /proc/{pid}/smaps
    std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
    UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
    if (fp == nullptr) return;
    // 2. 从文件中读取数据,read_mapinfo就是具体的解析规则了,就是把某一段内存某一个内存放在数组的某一个位置中
    read_mapinfo(fp.get(), stats, foundSwapPss);
}

下面具体来看一下 read_mapinfo

4.1.1 static void read_mapinfo(FILE fp, stats_t stats, bool* foundSwapPss)

static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss)
{
    char line[1024];
    int len, nameLen;
    bool skip, done = false;

    unsigned pss = 0, swappable_pss = 0, rss = 0;
    float sharing_proportion = 0.0;
    unsigned shared_clean = 0, shared_dirty = 0;
    unsigned private_clean = 0, private_dirty = 0;
    unsigned swapped_out = 0, swapped_out_pss = 0;
    bool is_swappable = false;
    unsigned temp;

    uint64_t start;
    uint64_t end = 0;
    uint64_t prevEnd = 0;
    char* name;
    int name_pos;

    int whichHeap = HEAP_UNKNOWN;
    int subHeap = HEAP_UNKNOWN;
    int prevHeap = HEAP_UNKNOWN;

    *foundSwapPss = false;

    if(fgets(line, sizeof(line), fp) == 0) return;

    while (!done) {
        prevHeap = whichHeap;
        prevEnd = end;
        whichHeap = HEAP_UNKNOWN;
        subHeap = HEAP_UNKNOWN;
        skip = false;
        is_swappable = false;

        len = strlen(line);
        if (len < 1) return;
        line[--len] = 0;

        if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
            skip = true;
        } else {
            while (isspace(line[name_pos])) {
                name_pos += 1;
            }
            name = line + name_pos;
            nameLen = strlen(name);
            // Trim the end of the line if it is " (deleted)".
            const char* deleted_str = " (deleted)";
            if (nameLen > (int)strlen(deleted_str) &&
                strcmp(name+nameLen-strlen(deleted_str), deleted_str) == 0) {
                nameLen -= strlen(deleted_str);
                name[nameLen] = '\0';
            }
            if ((strstr(name, "[heap]") == name)) {
                whichHeap = HEAP_NATIVE;
            } else if (strncmp(name, "[anon:libc_malloc]", 18) == 0) {
                whichHeap = HEAP_NATIVE;
            } else if (strncmp(name, "[stack", 6) == 0) {
                whichHeap = HEAP_STACK;
            } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
                whichHeap = HEAP_SO;
                is_swappable = true;
            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
                whichHeap = HEAP_JAR;
                is_swappable = true;
            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
                whichHeap = HEAP_APK;
                is_swappable = true;
            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
                whichHeap = HEAP_TTF;
                is_swappable = true;
            } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
                       (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
                whichHeap = HEAP_DEX;
                subHeap = HEAP_DEX_APP_DEX;
                is_swappable = true;
            } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) {
                whichHeap = HEAP_DEX;
                // Handle system@framework@boot* and system/framework/boot*
                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
                    subHeap = HEAP_DEX_BOOT_VDEX;
                } else {
                    subHeap = HEAP_DEX_APP_VDEX;
                }
                is_swappable = true;
            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
                whichHeap = HEAP_OAT;
                is_swappable = true;
            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) {
                whichHeap = HEAP_ART;
                // Handle system@framework@boot* and system/framework/boot*
                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
                    subHeap = HEAP_ART_BOOT;
                } else {
                    subHeap = HEAP_ART_APP;
                }
                is_swappable = true;
            } else if (strncmp(name, "/dev/", 5) == 0) {
                if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) {
                    whichHeap = HEAP_GL_DEV;
                } else if (strncmp(name, "/dev/ashmem", 11) == 0) {
                    if (strncmp(name, "/dev/ashmem/dalvik-", 19) == 0) {
                        whichHeap = HEAP_DALVIK_OTHER;
                        if (strstr(name, "/dev/ashmem/dalvik-LinearAlloc") == name) {
                            subHeap = HEAP_DALVIK_OTHER_LINEARALLOC;
                        } else if ((strstr(name, "/dev/ashmem/dalvik-alloc space") == name) ||
                                   (strstr(name, "/dev/ashmem/dalvik-main space") == name)) {
                            // This is the regular Dalvik heap.
                            whichHeap = HEAP_DALVIK;
                            subHeap = HEAP_DALVIK_NORMAL;
                        } else if (strstr(name, "/dev/ashmem/dalvik-large object space") == name ||
                                   strstr(name, "/dev/ashmem/dalvik-free list large object space")
                                       == name) {
                            whichHeap = HEAP_DALVIK;
                            subHeap = HEAP_DALVIK_LARGE;
                        } else if (strstr(name, "/dev/ashmem/dalvik-non moving space") == name) {
                            whichHeap = HEAP_DALVIK;
                            subHeap = HEAP_DALVIK_NON_MOVING;
                        } else if (strstr(name, "/dev/ashmem/dalvik-zygote space") == name) {
                            whichHeap = HEAP_DALVIK;
                            subHeap = HEAP_DALVIK_ZYGOTE;
                        } else if (strstr(name, "/dev/ashmem/dalvik-indirect ref") == name) {
                            subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
                        } else if (strstr(name, "/dev/ashmem/dalvik-jit-code-cache") == name ||
                                   strstr(name, "/dev/ashmem/dalvik-data-code-cache") == name) {
                            subHeap = HEAP_DALVIK_OTHER_CODE_CACHE;
                        } else if (strstr(name, "/dev/ashmem/dalvik-CompilerMetadata") == name) {
                            subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
                        } else {
                            subHeap = HEAP_DALVIK_OTHER_ACCOUNTING;  // Default to accounting.
                        }
                    } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) {
                        whichHeap = HEAP_CURSOR;
                    } else if (strncmp(name, "/dev/ashmem/libc malloc", 23) == 0) {
                        whichHeap = HEAP_NATIVE;
                    } else {
                        whichHeap = HEAP_ASHMEM;
                    }
                } else {
                    whichHeap = HEAP_UNKNOWN_DEV;
                }
            } else if (strncmp(name, "[anon:", 6) == 0) {
                whichHeap = HEAP_UNKNOWN;
            } else if (nameLen > 0) {
                whichHeap = HEAP_UNKNOWN_MAP;
            } else if (start == prevEnd && prevHeap == HEAP_SO) {
                // bss section of a shared library.
                whichHeap = HEAP_SO;
            }
        }

        //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
        //    isSqliteHeap, line);

        shared_clean = 0;
        shared_dirty = 0;
        private_clean = 0;
        private_dirty = 0;
        swapped_out = 0;
        swapped_out_pss = 0;

        while (true) {
            if (fgets(line, 1024, fp) == 0) {
                done = true;
                break;
            }

            if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) {
                /* size = temp; */
            } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) {
                rss = temp;
            } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) {
                pss = temp;
            } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
                shared_clean = temp;
            } else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
                shared_dirty = temp;
            } else if (line[0] == 'P' && sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
                private_clean = temp;
            } else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
                private_dirty = temp;
            } else if (line[0] == 'R' && sscanf(line, "Referenced: %d kB", &temp) == 1) {
                /* referenced = temp; */
            } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) {
                swapped_out = temp;
            } else if (line[0] == 'S' && sscanf(line, "SwapPss: %d kB", &temp) == 1) {
                *foundSwapPss = true;
                swapped_out_pss = temp;
            } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) {
                // looks like a new mapping
                // example: "10000000-10001000 ---p 10000000 00:00 0"
                break;
            }
        }

        if (!skip) {
            if (is_swappable && (pss > 0)) {
                sharing_proportion = 0.0;
                if ((shared_clean > 0) || (shared_dirty > 0)) {
                    sharing_proportion = (pss - private_clean
                            - private_dirty)/(shared_clean+shared_dirty);
                }
                swappable_pss = (sharing_proportion*shared_clean) + private_clean;
            } else
                swappable_pss = 0;

            stats[whichHeap].pss += pss;
            stats[whichHeap].swappablePss += swappable_pss;
            stats[whichHeap].rss += rss;
            stats[whichHeap].privateDirty += private_dirty;
            stats[whichHeap].sharedDirty += shared_dirty;
            stats[whichHeap].privateClean += private_clean;
            stats[whichHeap].sharedClean += shared_clean;
            stats[whichHeap].swappedOut += swapped_out;
            stats[whichHeap].swappedOutPss += swapped_out_pss;
            if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER ||
                    whichHeap == HEAP_DEX || whichHeap == HEAP_ART) {
                stats[subHeap].pss += pss;
                stats[subHeap].swappablePss += swappable_pss;
                stats[subHeap].rss += rss;
                stats[subHeap].privateDirty += private_dirty;
                stats[subHeap].sharedDirty += shared_dirty;
                stats[subHeap].privateClean += private_clean;
                stats[subHeap].sharedClean += shared_clean;
                stats[subHeap].swappedOut += swapped_out;
                stats[subHeap].swappedOutPss += swapped_out_pss;
            }
        }
    }
}

上述代码就是对照着smaps文件具体去解析和保存文件,比较简单,就不在赘述了。

4.2 read_memtrack_memory讲解

图形的内存消耗并不在/proc下面记录,需要调用获取/system/core/libmemtrack/memtrack.cpp

static int read_memtrack_memory(struct memtrack_proc* p, int pid,
        struct graphics_memory_pss* graphics_mem)
{
    int err = memtrack_proc_get(p, pid);
    if (err != 0) {
        ALOGW("failed to get memory consumption info: %d", err);
        return err;
    }

    ssize_t pss = memtrack_proc_graphics_pss(p);
    if (pss < 0) {
        ALOGW("failed to get graphics pss: %zd", pss);
        return pss;
    }
    graphics_mem->graphics = pss / 1024;

    pss = memtrack_proc_gl_pss(p);
    if (pss < 0) {
        ALOGW("failed to get gl pss: %zd", pss);
        return pss;
    }
    graphics_mem->gl = pss / 1024;

    pss = memtrack_proc_other_pss(p);
    if (pss < 0) {
        ALOGW("failed to get other pss: %zd", pss);
        return pss;
    }
    graphics_mem->other = pss / 1024;

    return 0;
}

5 stats_t保存的内存信息介绍

struct stats_t {
// “Resident Set Size”,实际驻留”在内存中”的内存数
int pss;
int swappablePss;
// Private Rss, Rss中私有的内存页面
int rss;
// Private_Dirty: Rss中已改写的私有页面页面
int privateDirty;
// Shared_Dirty: Rss和其他进程共享的已改写页面
int sharedDirty;
// Private_Clean: Rss中改写的私有页面页面
int privateClean;
// Shared_Clean: Rss中和其他进程共享的未改写页面
int sharedClean;
int swappedOut;
int swappedOutPss;
};

相关文章

网友评论

      本文标题:Android 内存信息源码解析

      本文链接:https://www.haomeiwen.com/subject/kvsvaltx.html