说说iOS与内存管理(上)

说起内存管理,看似老生常谈,而真正掌握内存管理的核心其实并不简单。ARC/MRR以及“谁分配谁就负责释放”这种基本原则是很重要的,但不是本文要讨论的重点。之前本人还没在小站发过相关的文章,本篇文章中,我本人是想结合实际开发和调试中遇到的一些细节问题,来谈谈iOS的内存管理内在机制和调试方法

上一篇文章已经是4月份的了,时间飞快又过去了好久,小站5月份没有文章更新,罪过罪过。iOS开发当中的内存管理,可深可浅,一般应用程序开发过程当中可能并不需要关注太多,如果不是最近的调试,也许就不会有这么多心得来整理此文。

关于内存,我准备分为内存管理的基本原则、原理和调试方法、实际问题几部分整理。那么接下来我就和大家一起复习和稍微深入一下iOS的内存管理的原理和原则。

0. 概述

内存,简单来说就是内部存储,复杂来说要从冯·诺依曼计算机结构说起。冯·诺依曼结构,也称做普林斯顿结构,目前和哈佛结构相对,指出了计算机由运算器、控制器、存储器、输入和输出设备几大部件组成。如今我们个人用的机器估计都是这个套路,而且运算器和控制器都合在一起,就是CPU,中央处理器。那么内存就是CPU能直接读写访问数据的地方(寄存器是在CPU内的,不算哈),有些朋友说谁谁谁的iPhone内存16G、64G,我只能说这个理解方法仅限于存储部件放在手机里(内)了,严格来讲这算“外存”,我们要讨论的不是这个。

冯·诺依曼结构还说了,内存是用来存啥的呢?指令+数据!(哈佛的恐怕就不一样了)对于我们开发者来说,指令基本就是代码逻辑,至于数据么变量常量肯定都算是的了。

内存有多大?不大,现今主流的个人机器也就几G的样子。iPhone?  统统1G。

我们操作系统都是运行在内存之上的,1G好像不算大,所以为了支持多进程,也为了支持大程序,抽象的虚拟存储的概念诞生了。

简要的概念先陈述到这,下面详细说。哦,对了,ARC和MRR我还是得提一下,这个要是真不知道还真的自己先去了解一下去。

1. 通用内存基本原理

说iOS的内存,有必要先看看一般的计算机都是怎么干的,iPhone也是计算机,通用的道理一样要遵循。这里提两方面:虚存的概念,内存内容的大致分布。

虚拟存储系统。刚刚提到了,物理内存就那么大点,但是还要跑多个程序,还要接受消耗很大内存的程序,这怎么办?凉拌。搞计算机的人都是很聪明的,在操作系统层面做了物理地址和逻辑地址之间的映射转换,当然处理器硬件上也做了支持。一个程序在运行时,实际要用到的指令和数据都是很有限的,不可能从头到尾同时用。那么对于一个程序来说,假装自己有非常大的空间,实际上只要有条理的把暂时要用到的部分放进物理内存供CPU访问就好,这样第二个问题解决了。那既然每个程序(进程)只用一小块,那整个物理内存就可以分给多个程序(进程)用了,第一个问题也迎刃而解。当然,这样做的前提是,数据和指令的动态进出,用完了的暂时不用的踢出内存,需要用的及时加载进来。这个具体的实现方式就多种多样了,很多实现方式是在外存中开了个交换区供换入换出,但iOS可略有不同。

内存的大致分布。不久以前,我发了一篇文章整理了Mach-O文件的格式分析,里面很复杂地放了好多东西,包括我们Build打包时的代码和数据。而Mach-O文件正是我们开发内容的一个静态展现形式,要想在运行的时候看样子,就得看这文件里包含的东西是怎么放进内存的。Objective-C是基于C的,不放看下C程序进程的内存分布:

一个运行时进程的典型内存分布(iOS大同小异)

一个运行时进程的典型内存分布(iOS大同小异)

最简单来说分为两大部分:指令+数据。再细分一点,五部分:代码(指令),初始化数据区,未初始化数据区,堆,栈。

  • 代码(指令,text)就不用说了,最静态的,就是只读的东西;
  • 初始化数据,简单理解就是有初始值的变量、常量;
  • 未初始化数据,只声明未给值的变量,运行前统统为0,之所以单独分出来,估计是性能考虑,因为这些东西都是0,没必要放在程序包里,也不用copy;
  • 栈,程序运行记录,每个线程,也就是每个执行序列各有一个(看crash log最容易理解),都是编译的时候能确定好的,还有一个特点就是这里面的数据可以不用指针,也不会丢;
  • 堆,最灵活的内存区,用途多多,动态分配和释放,编译时不能提前确定,我们的Objective-C对象都是这么来的,都存在这里,通常堆中的对象都是以指针来访问的,指针从线程栈中来,但不独属于某个线程,堆也是对复杂的运行时处理的基础支持,还有就是ARC还是MRR、“谁分配谁释放”说的都是堆上对象的管理;

其实,这个内存中的布局方式大部分操作系统中的大部分进程都是类似的。Objective-C的程序包对运行时有着复杂的支持和内容划分,但也都是在这个大的框架下进行的。

2. iOS的内存管理

其实,iOS的内存管理和其它操作系统大同小异。这里按照苹果文档所述,重点对堆内存分配整理下。

iOS的内存管理分为几个层面,从系统到libmalloc,ARC环境下,编译器也会帮助开发者做“力所能及”的优化处理。

首先,iOS和其它系统一样,操作系统内核会做虚拟存储到物理内存的映射管理,并做内存分页,每页4K。多个页构成一个内存区块统一管理,负责管理的对象是VM object,其中包含了pager、size、resident pages等诸多属性。所有的内存分配最终都将交由系统来处理(比如vm_allocate/mach_vm_allocate)。

而开发中,在系统内核的基础上,iOS使用libmalloc。不管是Objective-C的[NSObject alloc],还是C代码的对内存分配,重任都会落到malloc库上,释放也是如此,最终都将使用malloc库中的free()。malloc库中有很多malloc的同族函数可以动态分配内存。malloc库中定义了zone的概念,并实现了不同的zone(如nano zone和scalable zone),并根据内存需求的大小使用不通算法对nano、tiny、small、large量级的内存进行分配和释放管理。默认情况,在第一次调用malloc时,系统会生成一个default zone,后续的默认分配在此进行。比如,malloc_zone_xxx()函数最终都对特定的zone进行分配操作,执行zone->xxx()。每个zone都以链表的形式对已分配过的内存做cache处理,避免频繁对内核系统发起申请。malloc的内部实现都是开源的,感兴趣的可以去了解去看。

最后强调一下iOS特别需要注意的点:

当前的主流iPhone实际物理内存都不超过1G,可以说不算大。不过和Android机比起来,我不得不为苹果的设计称赞,1G空间利用得如此高效,性能不差,也控制了发热。

那么在这仅有的1G内存中,iOS的操作系统更是抛弃了不必要的复杂——系统层面不支持App内存页换出。当内存吃紧时,对于可以重新载入的只读数据来说,直接清理掉,而对于可写的数据,只能通过App自己去管理维护。内存紧张时,iOS会向App发起memory warning,不配合释放足够内存者,杀!

关于Instruments及内存调试,会在后续文章详细整理出来。

3. 其它

基本的原理就简要整理到此,如下是一些参考:

Memory Layout of C Programs

Anatomyof a program in memory

What and where are the stack and heap?

Memory Usage Performance Guidelines

A look at how malloc works on the Mac

Malloc库源码

相关文章:

此条目发表在 iOS, 开发, 计算机技术 分类目录,贴了 , , , 标签。将固定链接加入收藏夹。
  1. 期待博主下篇。 iOS 的内存实际使用大小是Activity Monitor中的real mem的值,但是和Vm tracker中的统计又有出入。

  2. 前天去医院做检查,看见一个小孩打针,其实也不小了,十多岁吧。他很害怕打针,这孩子哇一声哭了,边哭边喊疼。他妈怒了,还没打呢你哭什么。小孩说,哦,还没打啊,就不哭了……