iOS内存的基本原理
内存是如何初始化和被管理的?
- 在iOS内,指针的地址范围很大, 32位CPU上有 4GB 大小, 64位CPU上有 18EB 大小 (大约有2的60次方), 这么大的指针地址范围会致使我们看来系统内存有这么大,实际上物理内存可能没有这么大,这个大小被称为虚拟内存, 在OS X 中系统使用硬盘来存储内存中不经常使用的数据来作为内存的后备存储, 在内存中存有硬盘数据的指针地址, 需要的事后才写入内存.
- 然而, 在iOS中没有后备存储, iOS中只读类型的数据已经存在硬盘上, 在需要时写入内存使用, 可读写数据是常驻内存的,不会移除, 一旦可使用内存达到临界值, 系统会发出内存不够用的警告,由应用程序主动释放资源,若释放失败或没有排除警告,会直接被系统终结程序.
- 虚拟内存
iOS的物理内存被分割成大小为 4KB 的页, 而且不是所有的页都能被应用访问到. 虚拟内存是在kernel和应用层之间的一层,由于当我们每次需要内存的时候都直接调用kernel去申请内存的过程的代价是很大的, 所以底层会直接与虚拟内存申请空间, 虚拟内存会与kernel通信, 创建 VM Object来匹配物理内存,如图:
在内存的堆中, 我们应用的数据只占一部分, 其他还有framework创建的对象和缓存, 内存中还有一些静态常量, 线程的堆栈, 图片数据, CALayer的缓存, 数据库的缓存
内存类型: 干净内存和脏内存
干净内存: 从磁盘上拷贝到内存中的空间, 比如, 代码, framework,内存映射文件.
脏内存: 其他的内存空间. 比如 在堆上的初始化数据, 数据库缓存,解压的图片数据等.
大部分应用初始化的数据都是脏内存
举例如下:
- (void)displayWelcomeMessage {
NSString *welcomeMessage = [NSString stringWithUTF8String:“Welcome to WWDC!”];
self.alertView.title = welcomeMessage;
[self.alertView show];
}
welcomeMessage 是脏内存, 因为字符串Welcome to WWDC!
在静态数据区,它倍复制一份到堆上给了welcomeMessage.
- (void)displayWelcomeMessage {
NSString *welcomeMessage = @”Welcome to WWDC!”;
self.alertView.title = welcomeMessage;
[self.alertView show];
}
这次welcomeMessage是干净内存, 因为没有复制一份.
- (void)allocateSomeMemory {
void *buf = malloc(10 * 1024 * 1024);
…
}
虽然malloc是在堆上初始化数据, 但是 buf 没有实际存储数据, 所有buf是干净内存.
- (void)allocateSomeMemory {
void *buf = malloc(10 * 1024 * 1024);
for (unsigned int i = 0; i < sizeof(buf), i++) {
buf[i] = (char)random();
}
…
}
但是一旦使用了buf后, buf就是脏内存了.
UIImage *wwdcLogo = [UIImage imageNamed:@”WWDC12Logo”];
初始化UIImage,UIImage其实是CGImage的包装, CGImage生成jpeg 和 bitmap, 在内存中会有未压缩的bitmap数据,是脏内存.
当iOS运行在低内存是会发生什么
iOS开始的时候, 干净内存比例很大,当我们运行app的时候, 随着运行的进行, 初始化数据, 产生了脏内存, 进而是脏内存比例增大,干净内存比例减小, 最后产生内存压力, 内存不够用了, 这时候系统会终结掉后台应用, 释放属于应用的脏数据, 腾出内存空间.
内存警告
这是一次挑战
- 会发生在内存有限的设备上
- 是最后保护用户体验的机会
- 保证你的应用能响应内存警告,警告通知是在主线程上触发,避免重复的初始化大数据
这是一次机会
- 尽可能的释放内存,但是不要影响用户体验
- 内存警告传递到应用程序有几种方式:
- 通知
UIApplicationDidReceiveMemoryWarningNotification
- UIApplication代理方法
-[id <UIApplicationDelegate> -applicationDidReceiveMemoryWarning:]
- UIViewController方法
-[UIViewController didReceiveMemoryWarning]
- 通知
一定要注意脏内存,因为脏内存是由应用创建,如果不清除,只有应用被终结后才能释放, 使用Profile的VM Tracker去检测内存使用情况, 避免出现大范围经常性波动, 减少内存还可以使用 @autoreleasepool
.
找到内存的问题
- 减少内存的使用: 清楚我们应用的视图层级,只创建必要的视图. 避免循环的导致堆增长,不要忽略那些小的对象.
- 避免内存增长:
- 不可访问的,没有任何指针指向
- 不能被再次使用
- 被抛弃的内存
- 仍然有指针引用它, 但是被浪费的
- 从未被使用过
- 缓存
- 被引用了和等待使用
- 可能永远不会被使用了
如何检测内存问题
内存不应该在重复一个操作中持续增长, 比如: push和pop UIViewcontroller,滑动UITableview, 操作数据库搜索
使用工具和陷阱
使用 Allocations Instrument 来检测内存是否泄露, 遇到内存泄露可以检查是否在块中使用了self对象, 应该换成 __weak 修饰的self这样不增加计数引用.
更多的信息可以查阅官方手册.
Instruments Documentation
Instruments User Guide
Instruments User Reference
如有不正确,欢迎批评!