一般情况下应用程序是不需要考虑堆和栈的大小的,总是当作足够大来使用就能满足一般业务开发。但是事实上堆和栈都不是无上限的,过多的递归会导致栈溢出,过多的 alloc 变量会导致堆溢出。
例子
不得不说 Cocoa 的内存管理优化做得挺好的,单纯用 C++ 在 Mac 下编译后执行以下代码,递归 174671 次后挂掉:
#include <iostream>
#include <stdlib.h>
void test(int i) {
void* ap = malloc(1024);
std::cout << ++i << "
";
test(i);
}
int main() {
std::cout << "start!" << "
";
test(0);
return 0;
}
而在 iOS 上执行以下代码则怎么也不会挂,连 memory warning 都没有:
- (void)stackOverFlow:(int)i {
char * aLeak = malloc(1024);
NSLog(@"try %d", ++i);
[self stackOverFlow:i];
}
而且如果 malloc 的大小改成比 1024 大的如 10240,其内存占用的增长要远慢于 1024。这大概要归功于 Cocoa 的 Flyweight 设计模式,不过暂时还没能真的理解到其优化原理,猜测可能是虽然内存空间申请了但是一直没用到,针对这种循环 alloc 的场景,做了记录,等到用到内存空间了才真正给出空间。
原理
iOS 内存布局如下图所示:
在应用程序分配的内存空间里面,最低地址位是固定的代码段和数据段,往上是堆,用来存放全局变量,对于 ObjC 来说,就是 alloc 出来的变量,都会放进这里,堆不够用的时候就会往上申请空间。最顶部高地址位是栈,局部的基本类型变量都会放进栈里。 ObjC 的对象都是以指针进行操控的,局部变量的指针都在栈里,全局的变量在堆里,而无论是什么指针,alloc 出来的都在堆里,所以 alloc 出来的变量一定要记得 release。
对于 autorelease 变量来说,每个函数有一个对应的 autorelease pool,函数出栈的时候 pool 被销毁,同时调用这个 pool 里面变量的 dealloc 函数来实现其内部 alloc 出来的变量的释放。