iOS-Monitor-Platform
iOS-Monitor-Platform copied to clipboard
获取 App 内存不准
- (NSUInteger)getResidentMemory
{
struct mach_task_basic_info info;
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)& info, & count);
if (r == KERN_SUCCESS)
{
return info.resident_size;
}
else
{
return -1;
}
}
这个拿到的值和 Xcode 上的不一致啊,多了30-40MB 左右
@ifelseboyxx 你是和 Debug Navigator 中的内存比较么,这个问题我之前也发现了,用代码统计出来的内存会比 Xcode 的内存要多几十 MB
那这个问题有解决吗?
@ifelseboyxx @PrinceMarko 我在一台越狱的设备上使用 top 命令查看进程的使用内存,发现上述代码与 top 命令拿到的 RSIZE 是一致的,我测试的数据两者都是 74 MB,至于为什么会和 Debug Gauge 的 RAM 值少几十 MB,应该是 Debug Gauge 采集到内存值少统计了一些数据,具体可以参考这篇文章 http://iosdevelopertips.com/xcode/xcode-5-debug-gauges-to-monitor-memory-and-cpu-usage.html
Speaking of which, beware of trusting the memory gauge. It’s useful, but it doesn’t tell the whole story: – It doesn’t include OpenGL textures. If you’re using OpenGL you’re using a lot more than the gauge says (and if you leak textures you’re in big trouble ;) – It doesn’t include frameworks too. Not normally an issue since they’re shared between apps, but if you don’t support 64bit, when your app runs on a 64bit device it forces the OS to load 32 bit versions of the frameworks. That eats a big chunk of extra RAM you’re probably not anticipating. (64bit apps use more RAM, but after taking the framework issue into account 32bit apps on a 64bit device are usually worse!) – It doesn’t include system services your app uses. Typical example: if your app saves a photo, the media server daemon uses a big chunk of RAM to process and save the image.
至于上述代码通过 mach API 拿到进程使用的 RAM 值的这种方式,应该问题是不大,这种方式我查看其他手淘和手Q的线上代码,他们的 appUsedMemory 函数也是通过这种实现,所以可以放心使用,包括有本书《High Performance iOS Apps: Optimize Your Code for Better Apps》中的 Example 2-40 track available memory and memory used 也介绍了这种方式获取应用使用内存。
用resident_size这种方式取出来的值,当不断新增malloc时(不要free掉),resident_size会达到一个最大值,然后会降下来一些;继续malloc,resident_size会继续增长,然后下降。。。 总之就是会有一个峰值,后面的值就与实际malloc的值不匹配了,小很多,峰值会记录在resident_size_max变量中。
@simpleease 能贴出你的测试代码么?问题是 malloc 到一定程度后 resident_size 到一个拐点后就递减了,那有与 Debug Gauge 和 Instruments 的值对比么?
@aozhimin 测试的demo可以查看这个:https://github.com/simpleease/MemoryTest 我分别测试了两组: iPhone 5C(10.1.1) 和 iPhone 7(11.1.2)。在iOS11上面比较明显。 Debug Gauge打出来的值与实际分配的是相差不大的。
@simpleease 多谢提供 Demo,我这边测试了下,通过 resident_size 确实有您上面描述的这个问题,可以被重现,晚上我看下具体原因是什么。
@aozhimin 嗯,尝试过分析原因,由于iOS没有换页系统,猜测是不是苹果的一些优化策略,对一些只是初始化后面不再使用的相似内存会unload调,只是用了某个索引表来记录。
@simpleease 下图是 Allocations 和 VM Tracker 的走势
Allocations 和 Debug Gauge 一样都是一直递增的,但是 VM Tracker 中的 Resident Size 会到一个峰值然后还会下降一点,和通过 mach API 拿到的趋势基本一致了,API 本身应该没问题。但是为什么会出现这个现象还要进一步看看,你说的只能算是一种猜测,还需要去证明。
@simpleease 今天又去验证了一下,测试机器 iPhone 7 ,OS 11.1.2(15B202),使用 Instruments 的 Activity Monitor 模板去跑测试项目,在 Live Processes 一栏中有所有的存活的进程,其中有一列 Real Mem 就是对应进程使用的物理内存,通过 API 拿到的数据与这列的进行对比可以发现两者是一致的,Debug Gauge 应该是和 Allocations 差不多的实现,显示的并不是应用使用的物理内存。
Activity Monitor 的截图

API 对应的输出
2017-12-08 08:15:16.697415+0200 MemoryTest[1876:801381] getResidentMemory:684 MB
2017-12-08 08:15:16.707631+0200 MemoryTest[1876:801381] getResidentMemory:685 MB
2017-12-08 08:15:16.717447+0200 MemoryTest[1876:801381] getResidentMemory:686 MB
2017-12-08 08:15:16.727429+0200 MemoryTest[1876:801381] getResidentMemory:687 MB
2017-12-08 08:15:16.737571+0200 MemoryTest[1876:801381] getResidentMemory:688 MB
2017-12-08 08:15:16.747467+0200 MemoryTest[1876:801381] getResidentMemory:689 MB
2017-12-08 08:15:16.757514+0200 MemoryTest[1876:801381] getResidentMemory:691 MB
2017-12-08 08:15:16.767500+0200 MemoryTest[1876:801381] getResidentMemory:692 MB
2017-12-08 08:15:16.777527+0200 MemoryTest[1876:801381] getResidentMemory:693 MB
2017-12-08 08:15:16.787278+0200 MemoryTest[1876:801381] getResidentMemory:694 MB
2017-12-08 08:15:16.797505+0200 MemoryTest[1876:801381] getResidentMemory:695 MB
2017-12-08 08:15:16.807467+0200 MemoryTest[1876:801381] getResidentMemory:696 MB
2017-12-08 08:15:16.817518+0200 MemoryTest[1876:801381] getResidentMemory:697 MB
2017-12-08 08:15:16.827652+0200 MemoryTest[1876:801381] getResidentMemory:698 MB
2017-12-08 08:15:16.837327+0200 MemoryTest[1876:801381] getResidentMemory:682 MB
2017-12-08 08:15:16.847420+0200 MemoryTest[1876:801381] getResidentMemory:683 MB
2017-12-08 08:15:16.857550+0200 MemoryTest[1876:801381] getResidentMemory:684 MB
2017-12-08 08:15:16.867580+0200 MemoryTest[1876:801381] getResidentMemory:685 MB
2017-12-08 08:15:16.877496+0200 MemoryTest[1876:801381] getResidentMemory:686 MB
2017-12-08 08:15:16.887501+0200 MemoryTest[1876:801381] getResidentMemory:687 MB
2017-12-08 08:15:16.897399+0200 MemoryTest[1876:801381] getResidentMemory:688 MB
2017-12-08 08:15:16.907451+0200 MemoryTest[1876:801381] getResidentMemory:689 MB
2017-12-08 08:15:16.917416+0200 MemoryTest[1876:801381] getResidentMemory:690 MB
2017-12-08 08:15:16.927444+0200 MemoryTest[1876:801381] getResidentMemory:691 MB
2017-12-08 08:15:16.937500+0200 MemoryTest[1876:801381] getResidentMemory:692 MB
2017-12-08 08:15:16.947544+0200 MemoryTest[1876:801381] getResidentMemory:693 MB
2017-12-08 08:15:16.959844+0200 MemoryTest[1876:801381] getResidentMemory:694 MB
2017-12-08 08:15:16.967095+0200 MemoryTest[1876:801381] getResidentMemory:695 MB
2017-12-08 08:15:16.977509+0200 MemoryTest[1876:801381] getResidentMemory:697 MB
2017-12-08 08:15:16.998876+0200 MemoryTest[1876:801381] getResidentMemory:682 MB
<End of Run>
关于为什么到峰值会有一点下降,通过 VM Tracker 的数据的比对:
100% *All* 97 < multiple > 511.81 MiB 450.48 MiB 366.41 MiB 1.55 GiB 32%
89% *Dirty* 55 < multiple > 454.28 MiB 450.48 MiB 366.41 MiB 1.34 GiB 33%
86% MALLOC_LARGE 12 439.52 MiB 439.52 MiB 351.91 MiB 791.47 MiB 56%
6% __LINKEDIT 4 < multiple > 33.66 MiB 0 Bytes 0 Bytes 86.79 MiB 39%
4% __TEXT 10 < multiple > 19.25 MiB 0 Bytes 0 Bytes 27.07 MiB 71%
1% Performance tool data 7 5.94 MiB 5.94 MiB 14.50 MiB 32.77 MiB 18%
1% mapped file 11 < multiple > 4.62 MiB 0 Bytes 0 Bytes 105.19 MiB 4%
1% __DATA_CONST 3 < multiple > 3.45 MiB 96.00 KiB 0 Bytes 3.47 MiB 100%
0% MALLOC_NANO 1 2.41 MiB 2.41 MiB 0 Bytes 512.00 MiB 0%
0% MALLOC_SMALL 1 1.25 MiB 1.25 MiB 0 Bytes 24.00 MiB 5%
0% __DATA 5 < multiple > 832.00 KiB 384.00 KiB 0 Bytes 935.04 KiB 89%
0% MALLOC_TINY 2 416.00 KiB 416.00 KiB 0 Bytes 4.00 MiB 10%
0% MALLOC metadata 15 256.00 KiB 256.00 KiB 0 Bytes 256.00 KiB 100%
0% CoreAnimation 3 80.00 KiB 80.00 KiB 0 Bytes 96.00 KiB 83%
0% shared memory 3 64.00 KiB 64.00 KiB 0 Bytes 64.00 KiB 100%
0% CG raster data 1 32.00 KiB 32.00 KiB 0 Bytes 32.00 KiB 100%
0% Activity Tracing 1 32.00 KiB 32.00 KiB 0 Bytes 256.00 KiB 12%
0% Foundation 1 16.00 KiB 16.00 KiB 0 Bytes 16.00 KiB 100%
0% CG image 1 16.00 KiB 16.00 KiB 0 Bytes 16.00 KiB 100%
0% Kernel Alloc Once 1 16.00 KiB 16.00 KiB 0 Bytes 32.00 KiB 50%
0% __FONT_DATA 0 CoreText 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% Stack Guard 5 0 Bytes 0 Bytes 0 Bytes 80.00 KiB 0%
0% __UNICODE 0 CoreFoundation 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% __DATA_DIRTY 0 libc++.1.dylib 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% MALLOC guard page 10 0 Bytes 0 Bytes 0 Bytes 192.00 KiB 0%
0% Stack 0 thread c52a8 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
100% *All* 98 < multiple > 452.62 MiB 391.30 MiB 822.98 MiB 1.94 GiB 23%
87% *Dirty* 54 < multiple > 395.09 MiB 391.30 MiB 822.97 MiB 1.72 GiB 22%
85% MALLOC_LARGE 12 383.41 MiB 383.41 MiB 801.02 MiB 1.16 GiB 32%
7% __LINKEDIT 4 < multiple > 33.66 MiB 0 Bytes 0 Bytes 86.79 MiB 39%
4% __TEXT 10 < multiple > 19.25 MiB 0 Bytes 0 Bytes 27.07 MiB 71%
1% mapped file 11 < multiple > 4.62 MiB 0 Bytes 0 Bytes 105.19 MiB 4%
1% __DATA_CONST 3 < multiple > 3.45 MiB 96.00 KiB 0 Bytes 3.47 MiB 100%
1% Performance tool data 7 3.36 MiB 3.36 MiB 21.48 MiB 32.77 MiB 10%
0% MALLOC_NANO 1 2.39 MiB 2.39 MiB 16.00 KiB 512.00 MiB 0%
0% MALLOC_SMALL 1 880.00 KiB 880.00 KiB 400.00 KiB 24.00 MiB 4%
0% __DATA 5 < multiple > 832.00 KiB 384.00 KiB 0 Bytes 935.04 KiB 89%
0% MALLOC_TINY 2 336.00 KiB 336.00 KiB 80.00 KiB 4.00 MiB 8%
0% MALLOC metadata 15 256.00 KiB 256.00 KiB 0 Bytes 256.00 KiB 100%
0% CoreAnimation 3 64.00 KiB 64.00 KiB 0 Bytes 64.00 KiB 100%
0% shared memory 3 64.00 KiB 64.00 KiB 0 Bytes 64.00 KiB 100%
0% CG raster data 1 32.00 KiB 32.00 KiB 0 Bytes 32.00 KiB 100%
0% Activity Tracing 1 32.00 KiB 32.00 KiB 0 Bytes 256.00 KiB 12%
0% Foundation 1 16.00 KiB 16.00 KiB 0 Bytes 16.00 KiB 100%
0% CG image 1 16.00 KiB 16.00 KiB 0 Bytes 16.00 KiB 100%
0% Kernel Alloc Once 1 16.00 KiB 16.00 KiB 0 Bytes 32.00 KiB 50%
0% __FONT_DATA 0 CoreText 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% dylib 1 0 Bytes 0 Bytes 0 Bytes 16.00 KiB 0%
0% Stack Guard 5 0 Bytes 0 Bytes 0 Bytes 80.00 KiB 0%
0% __UNICODE 0 CoreFoundation 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% __DATA_DIRTY 0 libc++.1.dylib 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
0% MALLOC guard page 10 0 Bytes 0 Bytes 0 Bytes 192.00 KiB 0%
0% Stack 0 thread c52a8 0 Bytes 0 Bytes 0 Bytes 0 Bytes NaN
MALLOC_LARGE是代码中每次 Malloc 1MB 的内存,可以看到随时间推移,这部分有一些下降。
@aozhimin VM Tracker中的Swapped Size后面不为0,怎么解释呢?
@simpleease 这个我也注意到了,但是比较老的文档都表示 iOS 没有交换空间的实现,也没有公开的文档显示 iOS 新增了这一特性,但是 VM Tracker 的 Swapped Size 确实会在内存快速增长到一定值后,它也会从原来的初始值 0 随之增长,而且 Instrument-VMTracker 中也没提及 Swapped Size 列,所以有可能是新加的特性,但是确实找不到证据来证明。
https://forums.developer.apple.com/message/137873#137873
用phys_footprint就和xcode显示的内存一致了
@feel2d phys_footprint和code的显示接近, 变化趋势一致. 但数值并不一致,不同阶段统计值和xocde显示值的误差也不一样。
@kojiyijian ,我之前debug看的时候,xcode显示和自己log的基本很一致,太详细的再没有研究。你说的“不同阶段统计值和xocde显示值的误差也不一样“, 不同阶段是指?另外,phys_footprint 是几部分内存之和,Jetsam 和webcore好像都用了, http://newosxbook.com/articles/MemoryPressure.html
楼主还是没解释清楚为什么代码统计的内存会变大,能在详细解释下吗?不胜感激
@yinjining 如 @onerobot 所说,resident_size(驻留内存)确实无法反映应用的真实物理内存,而且 Xcode 的 Debug Gauge 使用的应该是 phys_footprint,这个从 WebKit 和 XNU 的源码都能够得到佐证。WebKit 代码
size_t memoryFootprint()
{
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
if (result != KERN_SUCCESS)
return 0;
return static_cast<size_t>(vmInfo.phys_footprint);
}
XNU 代码 中 Jetsam 中判断应用内存是否过大使用的也是 phys_footprint,2018 WWDC Session iOS Memory Deep Dive 对这块也有介绍,有兴趣可以去看下。
或许是resident_size不包括cleanMemory
task_vm_info_data_t info; mach_msg_type_number_t size = TASK_VM_INFO_COUNT; kern_return_t kerr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &size); if( kerr == KERN_SUCCESS ) { mach_vm_size_t totalSize = info.internal + info.compressed; NSLog(@"Memory in use (in bytes): %u", totalSize); return totalSize; } else { NSLog(@"Error with task_info(): %s", mach_error_string(kerr)); }
可以查看下这个form https://developer.apple.com/forums/thread/105088 , Apple 的工程师提供了建议,建议使用 phys_footprint
