一般情况下应用程序是不需要考虑堆和栈的大小的,总是当作足够大来使用就能满足一般业务开发。但是事实上堆和栈都不是无上限的,过多的递归会导致栈溢出,过多的 alloc 变量会导致堆溢出。

例子

不得不说 Cocoa 的内存管理优化做得挺好的,单纯用 C++ 在 Mac 下编译后执行以下代码,递归 174671 次后挂掉:

#include <iostream>
#include <stdlib.h>

void test(int i) {
    void* ap = malloc(1024);
    std::cout << ++i << "\n";
    test(i);
}

int main() {
    std::cout << "start!" << "\n";
    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 内存布局如下图所示:

iOS 堆栈的溢出

在应用程序分配的内存空间里面,最低地址位是固定的代码段和数据段,往上是堆,用来存放全局变量,对于 ObjC 来说,就是 alloc 出来的变量,都会放进这里,堆不够用的时候就会往上申请空间。最顶部高地址位是栈,局部的基本类型变量都会放进栈里。 ObjC 的对象都是以指针进行操控的,局部变量的指针都在栈里,全局的变量在堆里,而无论是什么指针,alloc 出来的都在堆里,所以 alloc 出来的变量一定要记得 release。

对于 autorelease 变量来说,每个函数有一个对应的 autorelease pool,函数出栈的时候 pool 被销毁,同时调用这个 pool 里面变量的 dealloc 函数来实现其内部 alloc 出来的变量的释放。

相关文章:

  • 2021-04-16
  • 2021-08-14
  • 2022-01-16
  • 2021-11-21
  • 2022-12-23
  • 2022-12-23
  • 2022-01-06
  • 2021-06-15
猜你喜欢
  • 2021-05-19
  • 2021-12-16
  • 2021-09-25
  • 2022-02-07
相关资源
相似解决方案