查看内存使用情况
使用adb dumpsys 命令 adb shell dumpsys meminfo 其中,package_name 也可以换成程序的pid,pid可以通过 adb shell top | grep app_name 来查找,下图是滴滴主端的内存使用情况
应用级内存
didi@bogon ~ adb shell dumpsys meminfo com.sdu.didi.psnger
Applications Memory Usage (in Kilobytes):
Uptime: 180975 Realtime: 180975
** MEMINFO in pid 7914 [com.sdu.didi.psnger] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 32081 31972 92 294 59904 41985 17918
Dalvik Heap 26859 26764 0 318 27257 16354 10903
Dalvik Other 6354 6348 0 0
Stack 1424 1424 0 0
Ashmem 1558 1544 0 0
Gfx dev 4942 1636 8 0
Other dev 27 0 24 0
.so mmap 13066 384 8904 199
.apk mmap 19677 80 16676 0
.ttf mmap 11 0 0 0
.dex mmap 48628 24 37604 0
.oat mmap 12470 0 3128 0
.art mmap 2754 1872 44 15
Other mmap 2227 8 1256 0
EGL mtrack 41100 41100 0 0
GL mtrack 6984 6984 0 0
Unknown 5939 5924 12 99
TOTAL 227026 126064 67748 925 87161 58339 28821
App Summary
Pss(KB)
------
Java Heap: 28680
Native Heap: 31972
Code: 66800
Stack: 1424
Graphics: 49728
Private Other: 15208
System: 33214
TOTAL: 227026 TOTAL SWAP PSS: 925
Objects
Views: 121 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 4 AssetManagers: 3
Local Binders: 75 Proxy Binders: 35
Parcel memory: 37 Parcel count: 131
Death Recipients: 1 OpenSSL Sockets: 7
SQL
MEMORY_USED: 945
PAGECACHE_OVERFLOW: 263 MALLOC_SIZE: 62
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 88 135 16/40/7 /storage/emulated/0/Android/data/com.sdu.didi.psnger/files/im/im_database_282680000258050.db
4 28 45 171/107/4 /data/user/0/com.sdu.didi.psnger/databases/dns_record.db
4 32 84 10/24/7 /data/user/0/com.sdu.didi.psnger/databases/ad
4 20 37 46/22/5 /data/user/0/com.sdu.didi.psnger/databases/location_info.db
4 24 41 5/19/2 /data/user/0/com.sdu.didi.psnger/databases/download_file.db
4 100 149 58/44/25 /data/user/0/com.sdu.didi.psnger/databases/DIDI_DATABASE
4 20 19 0/23/2 /data/user/0/com.sdu.didi.psnger/databases/audio_record_2
4 12 0/0/0 (attached) temp
4 20 56 3/15/3 /data/user/0/com.sdu.didi.psnger/databases/audio_record_2 (1)
重点关注如下几个字段:
(1) 私有(Clean and Dirty)内存
进程独占的内存,也就是应用进程销毁时系统可以直接回收的内存容量。 通常来说,“private dirty”内存是其最重要的部分,因为只被自己的进程使用。它只在内存中存储,因此不能做分页存储到外存(Android不支持swap)。 所有分配的Dalvik堆和本地堆都是“private dirty”内存;Dalvik堆和本地堆中和Zygote进程共享的部分是共享dirty内存。
(2) Total 的 PSS 信息
实际使用内存,这是另一种应用内存使用的计算方式,这个值就是我们应用真正占据的内存大小。 PSS会把跨进程的共享页也计算在内。任何独占的内存页直接计算它的PSS值,而和其它进程共享的页则按照共享的比例计算PSS值。例如,在两个进程间共享的页,计算进每个进程PPS的值是它的一半大小。 PSS计算方式的一个好处是:把所有进程的PSS值加起来就可以确定所有进程总共占用的内存。这意味着用PSS来计算进程的实际内存使用、进程间对比内存使用和总共剩余内存大小是很好的方式。
通常来说,只需关心Pss Total列和Private Dirty列就可以了。在一些情况下,Private Clean列和Heap Alloc列也会提供很有用的信息。
代码示例
/**
* 获取进程内存Private Dirty数据
*
* @param context
* @param pid
* 进程ID
* @return nativePrivateDirty、dalvikPrivateDirty、 TotalPrivateDirty
*/
public static long[] getPrivDirty(Context context, int pid) {
ActivityManager mAm = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
int[] pids = new int[1];
pids[0] = pid;
MemoryInfo[] memoryInfoArray = mAm.getProcessMemoryInfo(pids);
MemoryInfo pidMemoryInfo = memoryInfoArray[0];
long[] value = new long[3]; // Natvie Dalvik Total
value[0] = pidMemoryInfo.nativePrivateDirty;
value[1] = pidMemoryInfo.dalvikPrivateDirty;
value[2] = pidMemoryInfo.getTotalPrivateDirty();
return value;
}
/**
* 获取进程内存PSS数据
*
* @param context
* @param pid
* @return nativePss、dalvikPss、TotalPss
*/
public static long[] getPSS(Context context, int pid) {
long[] value = new long[3]; // Natvie Dalvik Total
if (pid >= 0) {
int[] pids = new int[1];
pids[0] = pid;
ActivityManager mAm = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo[] memoryInfoArray = mAm.getProcessMemoryInfo(pids);
MemoryInfo pidMemoryInfo = memoryInfoArray[0];
value[0] = pidMemoryInfo.nativePss;
value[1] = pidMemoryInfo.dalvikPss;
value[2] = pidMemoryInfo.getTotalPss();
} else {
value[0] = 0;
value[1] = 0;
value[2] = 0;
}
return value;
}
获取手机总内存和可用内存信息
“/proc/meminfo”文件记录了android手机的一些内存信息,通过读取文件”/proc/meminfo”的信息能够获取手机Memory的总量。
# cat /proc/meminfo
cat /proc/meminfo
MemTotal: 94096 kB 所有可用RAM大小。
MemFree: 1684 kB LowFree与HighFree的总和,被系统留着未使用的内存。
Buffers: 16 kB 用来给文件做缓冲大小
Cached: 27160 kB 被高速缓冲存储器(cache memory)用的内存的大小(等于diskcache minus SwapCache)。
SwapCached: 0 kB 被高速缓冲存储器(cache memory)用的交换空间的大小。已经被交换出来的内存,仍然被存放在swapfile中,用来在需要的时候很快的被替换而不需要再次打开I/O端口。
Active: 35392 kB 在活跃使用中的缓冲或高速缓冲存储器页面文件的大小,除非非常必要,否则不会被移作他用。
Inactive: 44180 kB 在不经常使用中的缓冲或高速缓冲存储器页面文件的大小,可能被用于其他途径。
Active(anon): 26540 kB
Inactive(anon): 28244 kB
Active(file): 8852 kB
Inactive(file): 15936 kB
Unevictable: 280 kB
Mlocked: 0 kB
SwapTotal: 0 kB 交换空间的总大小。
SwapFree: 0 kB 未被使用交换空间的大小。
Dirty: 0 kB 等待被写回到磁盘的内存大小。
Writeback: 0 kB 正在被写回到磁盘的内存大小。
AnonPages: 52688 kB 未映射页的内存大小。
Mapped: 17960 kB 设备和文件等映射的大小。
Slab: 3816 kB 内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗。
SReclaimable: 936 kB 可收回Slab的大小。
SUnreclaim: 2880 kB 不可收回Slab的大小(SUnreclaim+SReclaimable=Slab)。
PageTables: 5260 kB 管理内存分页页面的索引表的大小。
NFS_Unstable: 0 kB 不稳定页表的大小。
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 47048 kB
Committed_AS: 1483784 kB
VmallocTotal: 876544 kB
VmallocUsed: 15456 kB
VmallocChunk: 829444 kB
要获取android手机总内存大小,只需读取”/proc/meminfo”文件的第1行,并进行简单的字符串处理即可。
代码示例
/**
* 获取空闲内存和总内存拼接字符串
*
* @return 总内存
*/
public static String getFreeAndTotalMem() {
long[] memInfo = getMemInfo();
return Long.toString(memInfo[1] + memInfo[2] + memInfo[3]) + "M/"
+ Long.toString(memInfo[0]) + "M";
}
/**
* 获取内存信息:total、free、buffers、cached,单位MB
*
* @return 内存信息
*/
public static long[] getMemInfo() {
long memInfo[] = new long[4];
try {
Class<?> procClazz = Class.forName("android.os.Process");
Class<?> paramTypes[] = new Class[] { String.class, String[].class,
long[].class };
Method readProclines = procClazz.getMethod("readProcLines",
paramTypes);
Object args[] = new Object[3];
final String[] memInfoFields = new String[] { "MemTotal:",
"MemFree:", "Buffers:", "Cached:" };
long[] memInfoSizes = new long[memInfoFields.length];
memInfoSizes[0] = 30;
memInfoSizes[1] = -30;
args[0] = new String("/proc/meminfo");
args[1] = memInfoFields;
args[2] = memInfoSizes;
if (null != readProclines) {
readProclines.invoke(null, args);
for (int i = 0; i < memInfoSizes.length; i++) {
memInfo[i] = memInfoSizes[i] / 1024;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return memInfo;
}
总结
1)Pss/SharedDirty/Private Dirty三列是读取了/proc/process-id/smaps文件获取的,可以通过adb shell cat /proc/process-id/smaps来查看(需要root)。这是个普通的linux文件,描述了进程的虚拟内存区域的具体信息。
2)Native HeapSize/Alloc/Free三列是使用C函数mallinfo得到的。
3)Dalvik HeapSize/Alloc/Free并非该cpp文件产生,而是android的Debug类生成。
meminfo结果详细分析
Pss对应的TOTAL值:内存所实际占用的值。
Dalvik Heap Size:从RuntimetotalMemory()获得,DalvikHeap总共的内存大小。
Dalvik HeapAlloc:RuntimetotalMemory()-freeMemory() ,Dalvik Heap分配的内存大小。
Dalvik Heap Free:从RuntimefreeMemory()获得,DalvikHeap剩余的内存大小。
Dalvik Heap Size 约等于Dalvik HeapAlloc+ Dalvik HeapFree。
Cursor:/dev/ashmem/Cursor Cursor消耗的内存(KB)。
Ashmem:/dev/ashmem,匿名共享内存用来提供共享内存通过分配一个多个进程可以共享的带名称的内存块。
Other dev:/dev/,内部driver占用的在 “Otherdev”。
.so mmap:C 库代码占用的内存。
.jar mmap:Java 文件代码占用的内存。
.apk mmap:apk代码占用的内存。
.ttf mmap:ttf 文件代码占用的内存。
.dex mmap:Dex 文件代码占用的内存。
Other mmap:其他文件占用的内存。
私有(Clean and Dirty)内存:
进程独占的内存。也就是应用进程销毁时系统可以直接回收的内存容量。通常来说,“private dirty”内存是其最重要的部分,因为只被自己的进程使用。它只在内存中存储,因此不能做分页存储到外存(Android不支持swap)。所有分配的Dalvik堆和本地堆都是“private dirty”内存;Dalvik堆和本地堆中和Zygote进程共享的部分是共享dirty内存。
实际使用内存 (PSS):
这是另一种应用内存使用的计算方式,把跨进程的共享页也计算在内。任何独占的内存页直接计算它的PSS值,而和其它进程共享的页则按照共享的比例计算PSS值。例如,在两个进程间共享的页,计算进每个进程PPS的值是它的一半大小。PSS计算方式的一个好处是:把所有进程的PSS值加起来就可以确定所有进程总共占用的内存。这意味着用PSS来计算进程的实际内存使用、进程间对比内存使用和总共剩余内存大小是很好的方式。
通常来说,只需关心Pss Total列和Private Dirty列就可以了。在一些情况下,Private Clean列和Heap Alloc列也会提供很有用的信息。下面是一些应该查看的内存分配类型(行中列出的类型):
- Dalvik Heap:
应用中Dalvik分配使用的内存。Pss Total包含所有的Zygote分配(如上面PSS定义所描述的,共享跨进程的加权)。Private Dirty是应用堆独占的内存大小,包含了独自分配的部分和应用进程从Zygote复制分裂时被修改的Zygote分配的内存页。注意:新平台版本有Dalvik Other这一项。Dalvik Heap中的Pss Total和Private Dirty不包括Dalvik的开销,例如即时编译(JIT)和垃圾回收(GC),然而老版本都包含在Dalvik的开销里面。
- Heap Alloc:
是应用中Dalvik堆和本地堆已经分配使用的大小。它的值比Pss Total和Private Dirty大,因为进程是从Zygote中复制分裂出来的,包含了进程共享的分配部分。
- .so mmap和.dex mmap:
mmap映射的.so(本地) 和.dex(Dalvik)代码使用的内存。Pss Total 包含了跨应用共享的平台代码;Private Clean是应用独享的代码。通常来说,实际映射的内存大小要大一点——这里显示的内存大小是执行了当前操作后应用使用的内存大小。然而,.so mmap 的private dirty比较大,这是由于在加载到最终地址时已经为本地代码分配好了内存空间。
- Unknown:
无法归类到其它项的内存页。目前,这主要包含大部分的本地分配,就是那些在工具收集数据时由于地址空间布局随机化(Address Space Layout Randomization ,ASLR)不能被计算在内的部分。和Dalvik堆一样, Unknown中的Pss Total把和Zygote共享的部分计算在内,Unknown中的Private Dirty只计算应用独自使用的内存。
- TOTAL:
进程总使用的实际使用内存(PSS),是上面所有PSS项的总和。它表明了进程总的内存使用量,可以直接用来和其它进程或总的可以内存进行比较。Private Dirty和Private Clean是进程独自占用的总内存,不会和其它进程共享。当进程销毁时,它们(特别是Private Dirty)占用的内存会重新释放回系统。Dirty内存是已经被修改的内存页,因此必须常驻内存(因为没有swap);Clean内存是已经映射持久文件使用的内存页(例如正在被执行的代码),因此一段时间不使用的话就可以置换出去。
- ViewRootImpl:
进程中活动的根视图的数量。每个根视图与一个窗口关联,因此可以帮助确定涉及对话框和窗口的内存泄露。
- AppContexts和Activities:
当前驻留在进程中的Context和Activity对象的数量。可以很快的确认常见的由于静态引用而不能被垃圾回收的泄露的 Activity对象。这些对象通常有很多其它相关联的分配,因此这是追查大的内存泄露的很好办法。
注意:View 和 Drawable 对象也持有所在Activity的引用,因此,持有View 或 Drawable 对象也可能会导致应用Activity泄露。