同一个app, 没有任何改动, 使用xcode查看内存占用是118M, 而使用Instuments Allocations 查看是153M, 为什么会不一样呢?

iOS虚拟内存 ios虚拟内存物理内存_ios

iOS虚拟内存 ios虚拟内存物理内存_ide_02


一些内存相关的名词

虚拟内存VM

虚拟内存机制,主要包括内存管理单元MMU、内存映射、分段、分页。在iOS中,一页通常有16KB的内存空间。

分配内存的时候,先分配虚拟内存,然后使用的时候再映射到实际的物理内存。

一个VM Region指的是一段连续的虚拟内存页,这些页的属性都相同。

/* localized structure - cannot be safely passed between tasks of differing sizes */
/* Don't use this, use MACH_TASK_BASIC_INFO instead */
struct task_basic_info {
    integer_t       suspend_count;  /* suspend count for task */
    vm_size_t       virtual_size;   /* virtual memory size (bytes) */
    vm_size_t       resident_size;  /* resident memory size (bytes) */
    time_value_t    user_time;      /* total user run time for
                                     *  terminated threads */
    time_value_t    system_time;    /* total system run time for
                                     *  terminated threads */
    policy_t        policy;         /* default policy for new threads */
};

struct mach_task_basic_info {
    mach_vm_size_t  virtual_size;       /* virtual memory size (bytes) */
    mach_vm_size_t  resident_size;      /* resident memory size (bytes) */
    mach_vm_size_t  resident_size_max;  /* maximum resident memory size (bytes) */
    time_value_t    user_time;          /* total user run time for
                                         *  terminated threads */
    time_value_t    system_time;        /* total system run time for
                                         *  terminated threads */
    policy_t        policy;             /* default policy for new threads */
    integer_t       suspend_count;      /* suspend count for task */
};

VM分为Clean Memory和Dirty Memory。即:

虚拟内存 Virtual Memory = Dirty Memory + Clean Memory + Compressed Memory。

使用malloc函数,申请一段堆内存,则该内存为Clean的。一旦写入数据,通常这块内存会变成Dirty。

获取App申请到的所有虚拟内存:

- (int64_t)memoryVirtualSize {
    struct task_basic_info info;
    mach_msg_type_number_t size = (sizeof(task_basic_info_data_t) / sizeof(natural_t));
    kern_return_t ret = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    if (ret != KERN_SUCCESS) {
        return 0;
    }
    return info.virtual_size;
}

mach_task_self()表示获取当前的Mach task。

Clean Memory

可以简单理解为能够被写入数据的干净内存。对开发者而言是read-only,而iOS系统可以写入或移除。

  1. System Framework、Binary Executable占用的内存
  2. 可以被释放(Page Out,iOS上是压缩内存的方式)的文件,包括内存映射文件Memory mapped file(如image、data、model等)。内存映射文件通常是只读的。
  3. 系统中可回收、可复用的内存,实际不会立即申请到物理内存,而是真正需要的时候再给。
  4. 每个framework都有_DATA_CONST段,当App运行时使用到了某个framework,该framework对应的_DATA_CONST的内存就由clean变为dirty了。

注意:如果通过文件内存映射机制memory mapped file载入内存的,可以先清除这部分内存占用,需要的时候再从文件载入到内存。所以是Clean Memory。

Dirty Memory

主要强调不可被重复使用的内存。对开发者而言,已经写入数据。

  1. 被写入数据的内存,包括所有heap中的对象、图像解码缓冲(ImageIO, CGRasterData,IOSurface)。
  2. 已使用的实际物理内存,系统无法自动回收。
  3. heap allocation、caches、decompressed images。
  4. 每个framework的_DATA段和_DATA_DIRTY段。

iOS中的内存警告,只会释放clean memory。因为iOS认为dirty memory有数据,不能清理。所以,应尽量避免dirty memory过大。

Tis: Frameworks you link actually use clean memory and dirty memory.

Frameworks框架占用clean memory 也占用dirty memory.

要清楚地知道Allocations和Dirty Size分别是因为什么?

下方有测量实验,如+50dirty的操作,在release环境不生效,因iOS系统自动做了优化。

Compressed Memory

iOS设备没有swapped memory,而在iOS7之后采用Compressed Memory机制,一般情况下能将目标内存压缩至原有的一半以下。对于缓存数据或可重建数据,尽量使用NSCache或NSPurableData,收到内存警告时,系统自动处理内存释放操作。并且是线程安全的。

首先,针对那些有一段时间没有被访问的dirty pages(多个page),内存压缩器会对其进行压缩。但是,在这块内存再次被访问时,内存压缩器会对它解压以正确的访问。举个例子,某个Dictionary使用了3个page的内存,如果一段时间没有被访问同时内存吃紧,则系统会尝试对它进行压缩从3个page压缩为1个page从而释放出2个page的内存。但是如果之后需要对它进行访问,则它占用的page又会变为3个。

这里要注意,压缩内存机制,使得内存警告与释放内存变得稍微复杂一些。即,对于已经被压缩过的内存,如果尝试释放其中一部分,则会先将它解压。而解压过程带来的内存增大,可能得到我们并不期待的结果。如果选用NSDictionary之类的,内存比较紧张时,尝试将NSDictionary的部分内存释放掉。但若NSDictionary之前是压缩状态,释放需要先解压,解压过程可能导致内存增大而适得其反。

所以,我们平常开发所关心的内存占用其实是 Dirty Size和Compressed Size两部分,也应尽量优化这两部分。而Clean Memory一般不用太多关注。

Resident Memory

已经被映射到虚拟内存中的物理内存。而phys_footprint才是真正消耗的物理内存。

Resident Memory = Dirty Memory + Clean Memory that loaded in pysical memory。

获取App消耗的Resident Memory:

- (int64_t)memoryResidentSize {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(task_basic_info_data_t) / sizeof(natural_t);
    kern_return_t ret = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    if (ret != KERN_SUCCESS) {
        return 0;
    }
    return info.resident_size;
}

Memory Footprint

/*
* phys_footprint
*   Physical footprint: This is the sum of:
*     + (internal - alternate_accounting)
*     + (internal_compressed - alternate_accounting_compressed)
*     + iokit_mapped
*     + purgeable_nonvolatile
*     + purgeable_nonvolatile_compressed
*     + page_table
*
* internal
*   The task's anonymous memory, which on iOS is always resident.
*
* internal_compressed
*   Amount of this task's internal memory which is held by the compressor.
*   Such memory is no longer actually resident for the task [i.e., resident in its pmap],
*   and could be either decompressed back into memory, or paged out to storage, depending
*   on our implementation.
*
* iokit_mapped
*   IOKit mappings: The total size of all IOKit mappings in this task, regardless of
    clean/dirty or internal/external state].
*
* alternate_accounting
*   The number of internal dirty pages which are part of IOKit mappings. By definition, these pages
*   are counted in both internal *and* iokit_mapped, so we must subtract them from the total to avoid
*   double counting.
*/

App消耗的实际物理内存,包括:

  1. Dirty Memory
  2. Clean memory but loaded in pysical memory
  3. Page Table
  4. Compressed Memory
  5. IOKit used
  6. NSCache, Purgeable等

获取App的Footprint:

- (int64_t)memoryPhysFootprint {
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t ret = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo, &count);
    if (ret != KERN_SUCCESS) {
        return 0;
    }
    return vmInfo.phys_footprint;
}

XNU中Jetsam判断内存过大,使用的也是phys_footprint,而非resident size。phys_footprint是我们关注的重点.

获取设备的所有物理内存大小,可以使用

[NSProcessInfo processInfo].physicalMemory

内存测量结果

测量环境

iPhone 7, iOS 13.3。

Clean Memory

初始状态

类型

内存值(MB)

分析

resident

59

App消耗的内存

footprint

13

实际物理内存

VM

4770

App分配的虚拟内存

Xcode Navigator

14.3

footprint + 调试需要

加50MB的clean memory

代码为:

__unused char *buf = malloc(50 * 1024 * 1024);

类型

内存值(MB)

增量

分析

resident

60

+1

App消耗的内存

footprint

14

+1

实际物理内存

VM

4817

+47

App分配的虚拟内存

Xcode Navigator

14.3

+0

footprint + 调试需要

实际,仅增加50MB的VM,而这里额外会有1~2MB的footprint增加,猜测是用于内存映射所需的。

到达虚拟内存上限会报错: error: can't allocate region,但不会导致崩溃***。

同时,申请的过程不会耗时

再加50MB的clean memory

类型

内存值(MB)

增量

分析

resident

60

+0

App消耗的内存

footprint

14

+0

实际物理内存

VM

4868

+51

App分配的虚拟内存

Xcode Navigator

14.3

+0

footprint + 调试需要

Dirty Memory

Resident、footprint、VM都增加。是实实在在的内存消耗,各个工具都会统计。

初始状态

类型

内存值(MB)

分析

resident

59

App消耗的内存

footprint

13

实际物理内存

VM

4769

App分配的虚拟内存

Xcode Navigator

14.3

footprint + 调试需要

加50MB的dirty memory

代码为:

// 仅此一句,依然是仅申请虚拟内存,物理内存不会变
char *buf = malloc(50 * 1024 * 1024 * sizeof(char));

// 内存使用了,所以是实际的物理内存被使用了。即内存有数据了,变成dirty memory。
for (int i = 0; i < 50 * 1024 * 1024; i++) {
    buf[i] = (char)rand();
}

类型

内存值(MB)

增量

分析

resident

110

+51

App消耗的内存

footprint

64

+51

实际物理内存

VM

4817

+48

App分配的虚拟内存

Xcode Navigator

64.4

+50.1

footprint + 调试需要

实际增加了50MB的物理内存,Resident Memory也会变化,同时额外多了1~2MB。

申请过程比较耗时,超出上限会导致崩溃

但该操作仅在debug下生效,release环境不生效,应该是iOS系统自行的优化。

再加50MB的dirty memory

类型

内存值(MB)

增量

分析

resident

160

+50

App消耗的内存

footprint

114

+50

实际物理内存

VM

4868

+51

App分配的虚拟内存

Xcode Navigator

114.4

+50

footprint + 调试需要

Clean Memory + Dirty Memory

初始状态

类型

内存值(MB)

分析

resident

59

App消耗的内存

footprint

13

实际物理内存

VM

4770

App分配的虚拟内存

Xcode Navigator

14.3

footprint + 调试需要

加50MB的clean memory,使用其中10MB

代码为:

// 申请50MB的虚拟内存
char *buf = malloc(50 * 1024 * 1024 * sizeof(char));

// 实际只用了10MB,所以10MB的dirty memory
for (int i = 0; i < 10 * 1024 * 1024; i++) {
    buf[i] = (char)rand();
}

类型

内存值(MB)

增量

分析

resident

70

+11

App消耗的内存

footprint

24

+11

实际物理内存

VM

4817

+47

App分配的虚拟内存

Xcode Navigator

24.3

+10

footprint + 调试需要

申请了50MB,但实际仅使用了10MB,因此只有这10MB为Dirty Memory。

再加50MB的clean memory,使用其中10MB

类型

内存值(MB)

增量

分析

resident

80

+10

App消耗的内存

footprint

34

+10

实际物理内存

VM

4868

+51

App分配的虚拟内存

Xcode Navigator

34.3

+10

footprint + 调试需要

VM

初始状态

类型

内存值(MB)

分析

resident

59

App消耗的内存

footprint

13

实际物理内存

VM

4770

App分配的虚拟内存

Xcode Navigator

14.3

footprint + 调试需要

加100MB的VM

代码为:

vm_address_t address;
vm_size_t size = 100*1024*1024;
// VM Tracker中显示Memory Tag 200
vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(200) | VM_FLAGS_ANYWHERE);
// VM Tracker中显示VM_MEMORY_MALLOC_HUGE
// vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(VM_MEMORY_MALLOC_HUGE) | VM_FLAGS_ANYWHERE);

类型

内存值(MB)

增量

分析

resident

60

+1

App消耗的内存

footprint

14

+1

实际物理内存

VM

4867

+97

App分配的虚拟内存

Xcode Navigator

14.3

+0

footprint + 调试需要

这里,mach_task_self()表示在自己的进程空间内申请,size的单位是byte。使用参数VM_MAKE_TAG(200)给申请的内存提供一个Tag标记,该数字在VM Tracker中会有标记。

再加100MB的VM

类型

内存值(MB)

增量

分析

resident

60

+0

App消耗的内存

footprint

14

+0

实际物理内存

VM

4967

+100

App分配的虚拟内存

Xcode Navigator

14.3

+0

footprint + 调试需要

UIImage

图片大小:map.jpg: 9054*5945

初始状态

类型

内存值(MB)

分析

resident

60

App消耗的内存

footprint

14

实际物理内存

VM

4768

App分配的虚拟内存

Xcode Navigator

14.3

footprint + 调试需要

self.image = [UIImage imageNamed:@"map.jpg"]

类型

内存值(MB)

增量

分析

resident

61

+2

App消耗的内存

footprint

14

+0

实际物理内存

VM

4768

+0

App分配的虚拟内存

Xcode Navigator

14.4

+0.1

footprint + 调试需要

构建UIImage对象所需要的图片数据消耗其实不大。这里的数据指的是压缩的格式化数据。

self.imageView.image = self.image;

类型

内存值(MB)

增量

分析

resident

61

+0

App消耗的内存

footprint

92

+78

实际物理内存

VM

4845

+77

App分配的虚拟内存

Xcode Navigator

92

+77.6

footprint + 调试需要

这个阶段,需要将图片数据解码成像素数据bitmap,并渲染到屏幕上。解码过程非常消耗内存和CPU资源,且默认在主线程中执行会阻塞主线程。

关于这里的一些详细信息及优化(如异步解码图片数据,主线程渲染),请看后文。

结论

通过以上的比较,可以对各个内存类型有一个初步直观的认识。

  1. footprint是App实际消耗的物理内存
  2. resident是实际映射到虚拟内存的物理内存
  3. 通常看到的Xcode Navigator显示的最接近footprint,另外还有一些调试需要的内存。

几种内存查看方式的区别

Xcode Navigator

初略展示了真实的物理内存消耗。颜色表明了内存占用是否合理。Xcode Navigator = footprint + 调试需要。不跟踪VM。往往初略观察App的内存占用情况,不能作为精确的参考。

Instuments Allocations

这里显示的内存,其实只是整个App占用内存的一部分,即开发者自行分配的内存,如各种类实例等。简单而言,就是开发者自行malloc申请的。

Allocations中主要包含的是所有MALLOC_XXX方法申请的内存, VM Region和部分App进程创建的VM Region。

注意: 非动态的内存,以及部分其他动态库创建的VM Region并不在Allocations的统计范围内。比如主程序或者动态库的_DATA数据段,这些数据内存区域并非通过malloc分配,也就没有统计在All Heap Allocations中,所以你会发现All Heap Allocations往往会比较小。除非你自行使用malloc系列方法创建大内存块,否则很难看到All Heap Allocations有一个大的数值。

我们在实际的App中,大的内存占用一般都是类似于WebKit,ImageIO,CoreAnimation等虚拟内存区域(VM Region),这些VM Region一般由系统代码生成和管理,我们编写的代码如果间接引用了这些内存而没有释放,也就会造成大面积的内存泄漏。

总结一下Allocations统计的内容: 

  1. 主要是MALLOC_XXX, VM Region, 以及部分App进程创建的VM Region。
  2. 非动态的内存,及部分其他动态库创建的VM Region并不在Allocations的统计范围内。比如主程序或者动态库的_DATA数据段,这些数据内存区域并非通过malloc分配,也不会统计
  3. 主程序或动态库的_DATA数据段、Stack函数栈,并非通过malloc分配,因此不在Allocations统计内。

All Heap Allocations

  1. malloc
  2. CFData
  3. 其他手动申请的内存,如 *char buf = malloc(50 * 1024 * 1024 * sizeof(char));

Malloc

开发者手动分配的堆区域内存块,比如一些人脸检测模型等,还有一些C/C++代码中的。

All Anonymous VM

无法由开发者直接控制,一般由系统接口调用申请的。例如图片之类的大内存,属于All Anonymous VM -> VM: ImageIO_IOSurface_Data,其他的还有IOAccelerator与IOSurface等跟GPU关系比较密切的.

以下内容参考自:iOS 内存管理研究,总结得非常到位了。

(CGImage是一个可以惰性初始化(持有原始压缩格式DataBuffer),并且通过类似引用计数管理真正的Image Bitmap Buffer的设计,
只有渲染时通过RetainBytePtr拿到Bitmap Buffer塞给VRAM(IOSurface),不渲染时ReleaseBytePtr释放Bitmap Buffer,DataBuffer占用本身就小)。
通常我们使用UIImageView,系统会自动处理解码过程,在主线程上解码和渲染,会占用CPU,容易引起卡顿。
推荐使用ImageIO在后台线程执行图片的解码操作(可参考SDWebImageCoder)。但是ImageIO不支持webp。

ASDK的原理:拿空间换时间,换取流畅,牺牲内存,但内存开销比UIKit高。
	正常用一个全屏的UIImageView,直接用image = UIImage(named:xxx)来设置图片,要在主线程解码,但消耗内存反而较小,只有4MB(正常需要10MB)。
	应该是IOSurface对图片数据做了一些优化。但如果是非常大的图片就会阻塞,不建议直接渲染。
	CGImage是一个可以惰性初始化(持有原始压缩格式DataBuffer),并且通过类似ARC管理真正的Image Bitmap Buffer的设计。
	只有渲染时候通过RatainBytePtr拿到Bitmap Buffer塞给VRAM(IOSurface),不渲染时ReleaseBytePtr释放Bitmap Buffer,DataBuffer本身占用很小。
复制代码

VM: Stack

调用堆栈,一般不需要做啥。每个线程都需要500KB左右的栈空间,主线程1MB。

VM: CG raster data

SDWebImage的图片解码数据的缓存,为了避免渲染时在主线程解码导致阻塞。如果对于这一点比较介意,可以做相应设置即可, 但是不推荐这样设置, 频繁降低性能

常见堆栈:

mmap
CGDataProvicerCreateWithCopyOfData
CGBitmapContextCreateImage
[SDWebImageWebPCoder decodedImageWithData:]
[SDWebImageCodersManager decodedImageWithData:]
[SDImageCache diskImageForKey:data:options:]
[SDImageCache queryCacheOperationForKey:options:done:]_block_invoke

Instuments VM Tracker

interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions.

比较大块的内存占用,如WebKit、ImageIO、CoreAnimation等VM Region,一般由系统生成和管理。

  1. 数据段_DATA,如占用VM为10.6MB,Resident为6261KB,Dirty为1930KB。
  2. 数据段_DATA_CONST,每个framework都有,当App在运行时用到了该framework,则此段内存由clean变为dirty。如占用VM为33.9MB,Resident为31.5MB,Dirty为4466KB。
  3. 数据段_DATA_DIRTY,每个framework都有_DATA段和_DATA_DIRTY段,内存是dirty的。如占用VM为862KB,Resident为798KB,Dirty为451KB。
  4. 有_LINKEDIT,包含了方法和变量的元数据(位置、偏移量),及代码签名等信息。如占用VM为98MB,Resident为22.4MB,Dirty为0KB. 注意:Dirty为0.
  5. 代码段_TEXT,如占用VM为252.9MB,Resident为133.7MB,Dirty为80KB。 注意:Dirty几乎为0.
  6. mapped file,如占用VM为104.4MB,Resident为7472KB,Dirty为32KB。clean memory。
  7. shared memory,如占用VM为64KB,Resident为64KB,Dirty为64KB。
  8. unused but dirty shlib __DATA,如占用VM为721KB,Resident为721KB,Dirty为721KB。

其他比如MALLOC_LARGE,MALLOC_NANO等都是申请VM的时候设置的tag。

  1. MALLOC_LARGE, 如占用VM为384KB,Resident为384KB,Dirty为384KB。
  2. MALLOC_NANO, 如占用VM为512MB,Resident为1584KB,Dirty为1568KB。
  3. MALLOC_SMALL, 如占用VM为24MB,Resident为896KB,Dirty为800KB。
  4. MALLOC_TINY, 如占用VM为4096KB,Resident为432KB,Dirty为432KB。
  5. Stack, 如占用VM为2096KB,Resident为144KB,Dirty为128KB。
  6. Performance tool data, 调试所需,如占用VM为336KB,Resident为336KB,Dirty为336KB。

分析一个VM Tracker的截图

例如:

iOS虚拟内存 ios虚拟内存物理内存_Memory_03

Type All 那一行说明:

  1. App一共申请了1.55GB的虚拟内存
  2. App实际使用的虚拟内存(Resident + Swapped = 488.91MB + 157.75MB = 646.66GB)
  3. iOS Swapped 157.75MB。其实就是Compressed。
  4. 实际物理内存Resident Memory为488.91MB
  5. Resident Memory中一共包含Dirty Memory为371.91MB

VM Tracker中的内存Type

VM_Tracker如何识别出每个内存块的Type?答案即为vm_allocate函数调用时的最后一个参数flags。如MALLOC_TINY, MALLOC_SMALL, MALLOC_LARGE, ImageIO等。 vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(200) | VM_FLAGS_ANYWHERE); VM_FLAGS_ANYWHERE是flags中控制内存分配方式的flag,表示可以接受任意位置。

#define VM_FLAGS_FIXED          0x0000
#define VM_FLAGS_ANYWHERE       0x0001
#define VM_FLAGS_PURGABLE       0x0002
#define VM_FLAGS_4GB_CHUNK      0x0004
#define VM_FLAGS_RANDOM_ADDR    0x0008
#define VM_FLAGS_NO_CACHE       0x0010
#define VM_FLAGS_RESILIENT_CODESIGN     0x0020
#define VM_FLAGS_RESILIENT_MEDIA        0x0040
#define VM_FLAGS_OVERWRITE      0x4000  /* delete any existing mappings first */
复制代码

参考:iOS内存深入探索之VM Tracker

即 2个字节就可存储该flag,而int4个字节的剩下两个就可用于存储标记内存类型的Type了。
VM_MAKE_TAG可快速设置Type。
#define VM_MAKE_TAG(tag) ((tag) << 24)
将值左移24个bit,即3个字节,则一个字节表示内存类型。

苹果内置的Type有:
#define VM_MEMORY_MALLOC 1
#define VM_MEMORY_MALLOC_SMALL 2
#define VM_MEMORY_MALLOC_LARGE 3
#define VM_MEMORY_MALLOC_HUGE 4
#define VM_MEMORY_SBRK 5// uninteresting -- no one should call
#define VM_MEMORY_REALLOC 6
#define VM_MEMORY_MALLOC_TINY 7
#define VM_MEMORY_MALLOC_LARGE_REUSABLE 8
#define VM_MEMORY_MALLOC_LARGE_REUSED 9
所以,这个地方的Type即为VM Tracker中显示的Type。
而设置自己的数字也是为了快速定位到自己的虚拟内存。
复制代码

Xcode Memory Debugger

该工具可以非常方便地查看所有对象的内存使用情况、依赖关系,以及循环引用等。如果将其导出为memgraph文件,也可以使用一些命令来进行分析:

不过在开始之前我们首先需要开启Scheme -> Run -> Diagnostics -> Malloc Stack选项, 这样才能在右侧看到具体的malloc 堆栈信息。

iOS虚拟内存 ios虚拟内存物理内存_ide_04

 运行起来后, 选择Debug Memory  Graph

iOS虚拟内存 ios虚拟内存物理内存_ios_05

右侧堆栈信息只有在打开 上面的Malloc Stack选项才能看到

iOS虚拟内存 ios虚拟内存物理内存_ide_06

 想要把本次的数据导出, 选择file -> Export Memory  Graph

iOS虚拟内存 ios虚拟内存物理内存_ios_07

  vmmap

vmmap memory-info.memgraph
# 查看摘要
vmmap --summary memory-info.memgraph

结合shell中的grep、awk等命令,可以获得任何想要的内存数据。

# 查看所有dylib的Dirty Pages的总和
vmmap -pages memory-info.memgraph | grep '.dylib' | awk '{sum += $6} END { print "Total Dirty Pages:"sum}'
# 查看CG image相关的内存数据
vmmap memory-info.memgraph | grep 'CG image'

heap

查看堆内存

# 查看Heap上的所有对象
heap memory-info.memgraph
# 按照内存大小来排序
heap memory-info.memgraph -sortBySize
# 查看某个类的所有实例对象的内存地址
heap memory-info.memgraph -addresses all | 'MyDataObject'

leaks

# 查看是否有内存泄漏
leaks memory-info.memgraph
# 查看内存地址处的泄漏情况
leaks --traceTree [内存地址] memory-info.memgraph

malloc_history

需要开启Run->Diagnostics中的Malloc Stack功能,建议使用Live Allocations Only。则lldb会记录debug过程中的对象创建的堆栈,配合malloc_history,即可定位对象的创建过程。

malloc_history memory-info.memgraph [address]
malloc_history memory-info.memgraph --fullStacks [address]

几种内存测量方式的使用建议

通常情况下,

  1. 各个工具展示的内存值可能不一致,因为其统计的方式及包含内存类型不一致。
    如Xcode Navigator通常只反映物理内存的实际占用, 但是无细节信息; 
    Allocations有详细信息, 但是并没有合物理内存一一对应。
  2. 开发者自行分配的内存在堆(Heap)上,可以使用Allocations来查看即可。
  3. 开发者调用iOS系统接口也会导致大量内存分配,需要使用VM Tracker来查看。尤其是一些OpenGL渲染、CoreVideo所需、ImageIO等的大内存。
  4. 对于内存泄漏等,可以使用Leaks,或Xcode Memory Debugger即可。当然,并非所有的泄漏都能通过这些工具检测出来,还可以根据Memory Graph的情况来进行分析。
  5. Xcode Memory Debugger很强大。如果觉得打开Instruments很麻烦,可以在开发调试过程中将Memory Graph及时导出进行分析。