参考文章
要想了解什么是VM Regions,就得先了解什么是虚拟内存。当我们向系统申请内存时,系统并不会给你返回物理内存的地址,而是给你一个虚拟内存地址。每个进程都拥有相同大小的虚拟地址空间,对于32位的进程,可以拥有4GB的虚拟内存,64位进程则更多,可达16EB。只有我们开始使用申请到的虚拟内存时,系统才会将虚拟地址映射到物理地址上,从而让程序使用真实的物理内存。下面是一个简易示意图:
进程A和B都拥有1到4的虚拟内存。系统通过虚拟内存到物理内存的映射,让A和B都可以使用到物理内存。上图中物理内存是充足的,但是如果A占用了大部分内存,B想要使用物理内存的时候物理内存却不够该怎么办呢?在OSX上系统会将不活跃的内存块写入硬盘,一般称之为swapping out。iOS上则会通知App,让App清理内存,也就是我们熟知的Memory Warning。
堆区会被划分成很多不同的VM Region,不同类型的内存分配根据需求进入不同的VM Region。除了MALLOC_MEDIUM和MALLOC_SMALL外,还有MALLOC_TINY,MALLOC_LAEGE, MALLOC metadata等等。
VM Region Size:
我们在VM Track中可以看到,一个VM Region有4种size。
Dirty Size
Swapped Size
Resident Size
Virtual Size
Virtual Size顾名思义,就是虚拟内存大小,将一个VM Region的结束地址减去起始地址就是这个值。Resident Size指的是实际使用物理内存的大小。Swapped Size则是交换到硬盘上的大小,仅OSX可用。Dirty Size根据官方的解释我的理解是如果一个内存页想要被复用,必须将内容写到硬盘上的话,这个内存页就是Dirty的。下面是官方对Dirty Size的解释。secondary storage可以理解为硬盘。
malloc 和 calloc:
我们除了使用NSObject的alloc分配内存外,还可以使用c的函数malloc进行内存分配。malloc的内存分配当然也是先分配虚拟内存,然后使用的时候再映射到物理内存,不过malloc有一个缺陷,必须配合memset将内存区中所有的值设置为0。这样就导致了一个问题,malloc出一块内存区域时,系统并没有分配物理内存。然而,调用memset后,系统将会把malloc出的所有虚拟内存关联到物理内存上,因为你访问了所有内存区域。我们通过代码来验证一下。在main方法中,创建一个1024*1024的内存块,也就是1M。
malloc_zone_t 和 NSZone:
相信大家对NSZone并不陌生,allocWithZone或者copyWithZone这2个方法大家应该也经常见到。那么Zone究竟是什么呢?Zone可以被理解为一组内存块,在某个Zone里分配的内存块,会随着这个Zone的销毁而销毁,所以Zone可以加速大量小内存块的集体销毁。不过NSZone实际上已经被苹果抛弃。你可以创建自己的NSZone,然后使用allocWithZone将你的OC对象在这个NSZone上分配,但是你的对象还是会被分配在默认的NSZone里。例如:
static NSMutableSet *objs = nil;
if (objs == nil) { objs = [NSMutableSet new]; }
NSZone *testZone = NSCreateZone(1024, 1024, YES);
NSSetZoneName(testZone, @"Test Object Zone");
for (int i = 0; i < 1000; ++i) {
TestObject *obj = [TestObject allocWithZone:testZone];
[objs addObject:obj];
}
代码创建了1000个TestObject对象,但是最后其实都在系统默认床架的NSZone中,Test Object Zone中只有1个node,其中是用来存放Zone本身的信息的,如果你真的想用Zone内存机制,可以使用malloc_zone_t。通过下面的代码可以在自定义的zone上malloc内存块,例如:
malloc_zone_t *testZone = malloc_create_zone(1024, 0);
malloc_set_zone_name(testZone, "Test malloc zone");
for (int i = 0; i < 1000; ++i) {
malloc_zone_malloc(testZone, 300 * 4096);
}
最后运行的结果是我们的Test malloc zone中有1001个node,也就是1000个Test_zone_malloc出来的内存块加上zone本身的信息所占的内存块。
另外我们可以使用malloc_destroy_zone(testZone)一次性释放上面分配的所有内存。