iOS-Monitor-Platform icon indicating copy to clipboard operation
iOS-Monitor-Platform copied to clipboard

获取 App 内存不准

Open ifelseboyxx opened this issue 8 years ago • 21 comments

- (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 avatar Sep 22 '17 10:09 ifelseboyxx

@ifelseboyxx 你是和 Debug Navigator 中的内存比较么,这个问题我之前也发现了,用代码统计出来的内存会比 Xcode 的内存要多几十 MB

aozhimin avatar Sep 26 '17 05:09 aozhimin

那这个问题有解决吗?

KrisMarko avatar Sep 26 '17 12:09 KrisMarko

@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 也介绍了这种方式获取应用使用内存。

aozhimin avatar Sep 26 '17 15:09 aozhimin

用resident_size这种方式取出来的值,当不断新增malloc时(不要free掉),resident_size会达到一个最大值,然后会降下来一些;继续malloc,resident_size会继续增长,然后下降。。。 总之就是会有一个峰值,后面的值就与实际malloc的值不匹配了,小很多,峰值会记录在resident_size_max变量中。

simpleease avatar Nov 30 '17 14:11 simpleease

@simpleease 能贴出你的测试代码么?问题是 malloc 到一定程度后 resident_size 到一个拐点后就递减了,那有与 Debug Gauge 和 Instruments 的值对比么?

aozhimin avatar Dec 04 '17 13:12 aozhimin

@aozhimin 测试的demo可以查看这个:https://github.com/simpleease/MemoryTest 我分别测试了两组: iPhone 5C(10.1.1) 和 iPhone 7(11.1.2)。在iOS11上面比较明显。 Debug Gauge打出来的值与实际分配的是相差不大的。

simpleease avatar Dec 05 '17 03:12 simpleease

@simpleease 多谢提供 Demo,我这边测试了下,通过 resident_size 确实有您上面描述的这个问题,可以被重现,晚上我看下具体原因是什么。

aozhimin avatar Dec 05 '17 10:12 aozhimin

@aozhimin 嗯,尝试过分析原因,由于iOS没有换页系统,猜测是不是苹果的一些优化策略,对一些只是初始化后面不再使用的相似内存会unload调,只是用了某个索引表来记录。

simpleease avatar Dec 06 '17 07:12 simpleease

@simpleease 下图是 Allocations 和 VM Tracker 的走势 image Allocations 和 Debug Gauge 一样都是一直递增的,但是 VM Tracker 中的 Resident Size 会到一个峰值然后还会下降一点,和通过 mach API 拿到的趋势基本一致了,API 本身应该没问题。但是为什么会出现这个现象还要进一步看看,你说的只能算是一种猜测,还需要去证明。

aozhimin avatar Dec 06 '17 13:12 aozhimin

@simpleease 今天又去验证了一下,测试机器 iPhone 7 ,OS 11.1.2(15B202),使用 InstrumentsActivity Monitor 模板去跑测试项目,在 Live Processes 一栏中有所有的存活的进程,其中有一列 Real Mem 就是对应进程使用的物理内存,通过 API 拿到的数据与这列的进行对比可以发现两者是一致的,Debug Gauge 应该是和 Allocations 差不多的实现,显示的并不是应用使用的物理内存。

Activity Monitor 的截图

image

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 avatar Dec 08 '17 07:12 aozhimin

@aozhimin VM Tracker中的Swapped Size后面不为0,怎么解释呢?

simpleease avatar Dec 08 '17 08:12 simpleease

@simpleease 这个我也注意到了,但是比较老的文档都表示 iOS 没有交换空间的实现,也没有公开的文档显示 iOS 新增了这一特性,但是 VM Tracker 的 Swapped Size 确实会在内存快速增长到一定值后,它也会从原来的初始值 0 随之增长,而且 Instrument-VMTracker 中也没提及 Swapped Size 列,所以有可能是新加的特性,但是确实找不到证据来证明。

aozhimin avatar Dec 08 '17 10:12 aozhimin

https://forums.developer.apple.com/message/137873#137873

27629678 avatar Mar 13 '18 05:03 27629678

用phys_footprint就和xcode显示的内存一致了

sohotz avatar Mar 19 '18 06:03 sohotz

@feel2d phys_footprint和code的显示接近, 变化趋势一致. 但数值并不一致,不同阶段统计值和xocde显示值的误差也不一样。

kojiyijian avatar Mar 19 '18 08:03 kojiyijian

@kojiyijian ,我之前debug看的时候,xcode显示和自己log的基本很一致,太详细的再没有研究。你说的“不同阶段统计值和xocde显示值的误差也不一样“, 不同阶段是指?另外,phys_footprint 是几部分内存之和,Jetsam 和webcore好像都用了, http://newosxbook.com/articles/MemoryPressure.html

sohotz avatar Mar 19 '18 09:03 sohotz

楼主还是没解释清楚为什么代码统计的内存会变大,能在详细解释下吗?不胜感激

yinjining avatar Oct 10 '18 07:10 yinjining

@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 对这块也有介绍,有兴趣可以去看下。

aozhimin avatar Oct 13 '18 07:10 aozhimin

或许是resident_size不包括cleanMemory

Humble7 avatar Dec 04 '18 04:12 Humble7

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)); }

isee15 avatar Feb 18 '19 11:02 isee15

可以查看下这个form https://developer.apple.com/forums/thread/105088 , Apple 的工程师提供了建议,建议使用 phys_footprint Lark20210201-160826

JethroYe avatar Feb 01 '21 08:02 JethroYe