dumpsys meminfo执行流程(二)
来源:互联网 发布:下载app客户端软件 编辑:程序博客网 时间:2024/05/17 08:53
上一节介绍了dumpsys meminfo命令执行的大致流程,这一节要讲的是dumpsys meminfo如何从native层拿到原始的memory系统数据以及dumpsys meminfo各数据的组成。
我们知道Java要与native之间进行相互调用,必须通过JNI层,在dumpsys meminfo执行过程中需要从native拿到从kernel传上来的系统memory的相关数据,这就需要通过JNI层。
具体过程如下:
首先是两个从native拿数据,位于debug.java中的函数:
public static native void getMemoryInfo(MemoryInfo memoryInfo);
public static native void getMemInfo(long[] outSizes);
先看getMemoryInfo()函数,此函数是调用的native的函数,经过jni转化:
{ "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)V", (void*) android_os_Debug_getDirtyPagesPid },
调用native的函数android_os_Debug_getDirtyPagesPid():
static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, jint pid, jobject object)//更新Java层pss相关的信息{ bool foundSwapPss = false; stats_t stats[_NUM_HEAP]; memset(&stats, 0, sizeof(stats)); load_maps(pid, stats, &foundSwapPss);//对当前进程的smaps的信息进行统计,统计完成之后放到stats[]数组中 struct graphics_memory_pss graphics_mem; if (read_memtrack_memory(pid, &graphics_mem) == 0) {//获取graphic的memory消耗数据,graphic所占用的数据必须单独统计 stats[HEAP_GRAPHICS].pss = graphics_mem.graphics; stats[HEAP_GRAPHICS].privateDirty = graphics_mem.graphics; stats[HEAP_GL].pss = graphics_mem.gl; stats[HEAP_GL].privateDirty = graphics_mem.gl; stats[HEAP_OTHER_MEMTRACK].pss = graphics_mem.other; stats[HEAP_OTHER_MEMTRACK].privateDirty = graphics_mem.other; } for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) {//将除native和dalvik所占用的内存除外的内存占用类型全部划为other_*** stats[HEAP_UNKNOWN].pss += stats[i].pss; stats[HEAP_UNKNOWN].swappablePss += stats[i].swappablePss; 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; } for (int i=0; i<_NUM_CORE_HEAP; i++) {//将other_**, native_***和dalvik_***全部放到java层的meminfo类中对应的变量中,具体如何操作将在后面讲到 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].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); } env->SetBooleanField(object, hasSwappedOutPss_field, foundSwapPss == false ? JNI_FALSE : JNI_TRUE); 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++) {//将之前统计在other_***类型内存中的数据进行整理,存放在Java的meminfo类的otherstat[]数组当中 otherArray[j++] = stats[i].pss; otherArray[j++] = stats[i].swappablePss; 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; } env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);}
android_os_Debug_getDirtyPagesPid()调用的是load_maps()函数:
static void load_maps(int pid, stats_t* stats, bool* foundSwapPss){ char tmp[128]; FILE *fp; sprintf(tmp, "/proc/%d/smaps", pid); fp = fopen(tmp, "r");//打开proc/<pid>/smaps文件 if (fp == 0) return; read_mapinfo(fp, stats, foundSwapPss);//解析smaps文件 fclose(fp);}接着调用的是read_mapinfo():
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; 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) {//循环解析smaps每一行的内容,将这些所占用的内存进行分类,然后将所得到的各类的内存值再根据不同的pss进行累加,最后的结果存放在stats[]数组里面 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; 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; 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_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_INDIRECT_REFERENCE_TABLE; } else if (strstr(name, "/dev/ashmem/dalvik-jit-code-cache") == name) { subHeap = HEAP_DALVIK_CODE_CACHE; } else { subHeap = HEAP_DALVIK_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) { /* resident = 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].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) { stats[subHeap].pss += pss; stats[subHeap].swappablePss += swappable_pss; 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的解析过程,下面看proc/meminfo的解析过程:
public static native void getMemInfo(long[] outSizes);通过JNI接口调用native的android_os_Debug_getMemInfo():
{ "getMemInfo", "([J)V", (void*) android_os_Debug_getMemInfo },
static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray out)//更新Java层的meminfo相关的数据信息到MemInfoReader类的mInfos数组当中{ char buffer[1024]; size_t numFound = 0; if (out == NULL) {//参数out是Java层getMemInfo(mInfos)中的nInfos. jniThrowNullPointerException(env, "out == null"); return; } int fd = open("/proc/meminfo", O_RDONLY);//打开/proc/meminfo if (fd < 0) { ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno)); return; } int len = read(fd, buffer, sizeof(buffer)-1); close(fd); if (len < 0) { ALOGW("Empty /proc/meminfo"); return; } buffer[len] = 0; static const char* const tags[] = { "MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", "Slab:", "SwapTotal:", "SwapFree:", "ZRam:", "Mapped:", "VmallocUsed:", "PageTables:", "KernelStack:", NULL }; static const int tagsLen[] = { 9, 8, 8, 7, 6, 5, 10, 9, 5, 7, 12, 11, 12, 0 }; long mem[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; char* p = buffer; while (*p && numFound < (sizeof(tagsLen) / sizeof(tagsLen[0]))) {//获取以上制定的tag对应的内存值 int i = 0; while (tags[i]) { if (strncmp(p, tags[i], tagsLen[i]) == 0) { p += tagsLen[i]; while (*p == ' ') p++; char* num = p; while (*p >= '0' && *p <= '9') p++; if (*p != 0) { *p = 0; p++; } mem[i] = atoll(num); numFound++; break; } i++; } while (*p && *p != '\n') { p++; } if (*p) p++; } mem[MEMINFO_ZRAM_TOTAL] = get_zram_mem_used() / 1024; // Recompute Vmalloc Used since the value in meminfo // doesn't account for I/O remapping which doesn't use RAM. //mem[MEMINFO_VMALLOC_USED] = get_allocated_vmalloc_memory() / 1024; int maxNum = env->GetArrayLength(out);//由于只是对一个数组进行赋值,所以直接通过这三步就可以完成Java层数据的设置,(1)此步骤为获取out数组的大小 if (maxNum > MEMINFO_COUNT) { maxNum = MEMINFO_COUNT; } jlong* outArray = env->GetLongArrayElements(out, 0);(2)可以理解为获取out数组的首地址 if (outArray != NULL) {//完成赋值将获取到的指定tag的meminfo的值赋到Java层的MemInfoReader类的mInfos[]全局数组当中 for (int i=0; i<maxNum; i++) { outArray[i] = mem[i]; } } env->ReleaseLongArrayElements(out, outArray, 0);(3)最后释放outArray}
回到调用的顺序中的JNI转化关系数组中的:
{ "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)V", (void*) android_os_Debug_getDirtyPagesPid },{ "getMemInfo", "([J)V", (void*) android_os_Debug_getMemInfo },此数组当中第一个getMemInfo是Java层调用的API,而android_os_Debug_getMemInfo则代表的是native层实际执行的函数。
而中间的第二个参数则是从Java层传到native的参数列表
以上第一个参数描述符 "(ILandroid/os/Debug$MemoryInfo;)V"代表函数返回值是void型,参数是一个MemInfo的class,而"([J)V"则代表的是void型的返回值,参数是一个long型的数组,数组大小已经在Java层设定好。 "(ILandroid/os/Debug$MemoryInfo;)的意思应该是指参数是android.os.Debug类的子类($的含义)Memoryinfo.
第二个参数在jni中和Java中的对照表如下(以下部分内容来自网络):
类描述符的表示方式:
类描述符是类的完整名称(包名+类名),将原来的 . 分隔符换成 / 分隔符。
例如:android.os.debug类的类描述符是android/os/debug
数组类型的描述符则为,则为: [ + 其类型的域描述符:
例如:
int [ ] 其描述符为[I
float [ ] 其描述符为[F
String [ ] 其描述符为[Ljava/lang/String;
1. 基本的域描述符:
2. 引用类型的描述符:
一般引用类型则为 L + 该类型类描述符 + ; (注意,这儿的分号“;”只得是JNI的一部分,而不是我们汉语中的分段,下同)
例如:String类型的域描述符为 Ljava/lang/String;
对于数组,其为 : [ + 其类型的域描述符 + ;
int[ ] 其描述符为[I
float[ ] 其描述符为[F
String[ ] 其描述符为[Ljava/lang/String;
Object[ ]类型的域描述符为[Ljava/lang/Object;
多维数组则是 n个[ +该类型的域描述符 , N代表的是几维数组。例如:
int [ ][ ] 其描述符为[[I
float[ ][ ] 其描述符为[[F
方法描述符
将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下: (参数的域描述符的叠加)返回
类型描述符。对于,没有返回值的,用V(表示void型)表示。举例如下:
Java层方法 JNI函数签名
String test ( ) ()Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[ ] bytes) ([B)V
以上是摘自网络关于JNI参数的规则讲解,下面再来介绍native是如何把数据写到Java的变量里的。
首先看初始化阶段的register_android_os_Debug()函数的信息:
int register_android_os_Debug(JNIEnv *env){…… jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");//获取MemoryInfo类对应于native中的jclass实例 otherStats_field = env->GetFieldID(clazz, "otherStats", "[I");//获取clazz类中的参数otherStats的ID,后面需要通过看这个ID来设置Java层对应数据的值 for (int i=0; i<_NUM_CORE_HEAP; i++) {//循环获取clazz类中的参数ID,包括other_***, native_***以及dalvik_***三个类型的值的ID stat_fields[i].pss_field = env->GetFieldID(clazz, stat_field_names[i].pss_name, "I");//获取clazz类中的参数获取clazz类中的参数stat_field_names[i].pss_name的ID,后面需要通过看这个ID来设置Java层对应数据的值 stat_fields[i].pssSwappable_field = env->GetFieldID(clazz, stat_field_names[i].pssSwappable_name, "I"); stat_fields[i].privateDirty_field = env->GetFieldID(clazz, stat_field_names[i].privateDirty_name, "I"); stat_fields[i].sharedDirty_field = env->GetFieldID(clazz, stat_field_names[i].sharedDirty_name, "I"); stat_fields[i].privateClean_field = env->GetFieldID(clazz, stat_field_names[i].privateClean_name, "I"); stat_fields[i].sharedClean_field = env->GetFieldID(clazz, stat_field_names[i].sharedClean_name, "I"); stat_fields[i].swappedOut_field = env->GetFieldID(clazz, stat_field_names[i].swappedOut_name, "I"); stat_fields[i].swappedOutPss_field = env->GetFieldID(clazz, stat_field_names[i].swappedOutPss_name, "I"); }return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));//完成Java到native方法的映射工作其中env就是要使用的JNIenv,android/os/Debug,指的是Debug.Java中的Debug类,加上头上的package name:android.os,固类名为android.os.Debug,gMethod是前面数组描述的Java方法到native方法的映射,NELEM(gMethod)指方法数组的大小}
获取到了相关的参数ID之后。接下来就是赋值:
static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, jint pid, jobject object)//更新Java层pss相关的信息{………… for (int i=0; i<_NUM_CORE_HEAP; i++) {//通过之前获取的参数ID,用SetIntField()函数对MemoryInfo类中的对应的变量进行赋值 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].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); } env->SetBooleanField(object, hasSwappedOutPss_field, foundSwapPss == false ? JNI_FALSE : JNI_TRUE); 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++) {//将之前统计在other_***类型内存中的数据分开进行整理,存放在otherArray中也就是Java的meminfo类的otherstat[]数组当中 otherArray[j++] = stats[i].pss; otherArray[j++] = stats[i].swappablePss; 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; } env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);//释放该资源}
这一节就这么多内容,下一节将介绍dumpsys meminfo关于系统内存总结信息是如何计算得出的。
- dumpsys meminfo执行流程(二)
- dumpsys meminfo执行流程(一)
- dumpsys meminfo执行流程(三)
- Dumpsys meminfo
- adb shell dumpsys meminfo
- dumpsys meminfo 说明
- Android procrank && dumpsys meminfo使用详解
- Android使用procrank和dumpsys meminfo分析内存占用情况
- Android使用procrank和dumpsys meminfo分析内存占用情况
- 通过dumpsys meminfo命令查看一个进程的内存情况
- procrank和dumpsys meminfo的pss大小不同的原因
- springmvc执行流程(二)
- meminfo
- adb shell dumpsys meminfo <packageName> [-d] 查看app(进程)的内存占用(未完待续)
- dumpsys
- dumpsys
- dumpsys
- dumpsys
- [LeetCode] 70. Climbing Stairs(斐波那契数列)
- [LeetCode] 73. Set Matrix Zeroes
- [LeetCode] 88. Merge Sorted Array
- 小程序支付常见问题解答
- java面试
- dumpsys meminfo执行流程(二)
- [LeetCode] 12. Integer to Roman
- [LeetCode] 13. Roman to Integer
- 操作符笔记
- [Java] 各种流的分类及区别
- [Java] 各种常用函数
- [Java] 文件上传下载项目(详细注释)
- ZooKeeper典型应用场景一览
- [Java] 为什么要定义接口