窥探内存管理
关于内存的问题是一个很初级的问题也是一个很高深的问题,确切的说是个入门必须了解的问题也是很有内涵的问题。如何认识内存,使用内存,管理内存是一个一直贯穿于编码过程的核心问题。抛弃所有的架构概念,剥离覆盖在软件开发的迷雾,程序本身只是一个内存中数据不断的迁移与CPU计算器不断进行数值运算的过程。一层层的高级语言和软件工程是将这个复杂过程的更加条理和有序的去组织,去除了“重复制造车轮”的烦恼。但是内存的问题本身是不可回避的,因为程序最终是要运行在内存中要通过CPU进行运算的。
准备写这篇文章的的起因是在IOS开发过程中系统经常crash掉(这篇文章的草稿是2011年写的,感觉一直无法完善,过了这么长时间自己仍旧是那样的对内存管控一知半解,发出来请大家指正一下吧),其实问题很简单就是对内存没有处理好,在受限设备上内存的管理尤其重要。
我们先明确一下概念
程序本身分为数据段和代码端;
内存中的区域包括堆、栈、静态和全局存储区、常量存储区;
各种不同语言提供的内存管理机制也不同,C基本上可以理解为是手动管理;C++的stl提供了auto_ptr、boost(c++11)提供了shared_ptr,symbian C++是清理栈,com是引用计数器,Object-C是引用计数器和AutoReleasePools;而高级语言则纷纷提供了自动管理机制。
内存管理的基本原则是谁使用,谁管理,谁释放。
有时候这一条原则让我在相当长的一段时间内很是迷茫,用户获取了一个内存区域的控制权,进行修改,而提供内存区域的代码却无法知道这段内存区域的使用周期。为了提供一个可以修改的内存区域需要暴露出一个指针来,但这个指针的使用和修改却是不受提供者所控制的,此时所有权的变换使的我对这句话的理解产生了疑问。而在多个用户代码使用这个指针的时候如何去协调管理和释放的问题就成为了另一个疑问。很多时候往往会是为了解决程序的bug让他不至于立即crash而直接不去释放这段内存区域,一个简单的程序,这样做不会有什么问题,也不会有人去计较你对内存的粗暴占有,因为当你的程序结束的时候,系统会回收这一部分内存。即使是大的程序问题也可能会在在短时间内被隐藏,然而7x24运行的服务程序随着时间的推移,这些泄露内存对系统产生的影响会逐步的显现出来,此时必须应该去解决这一问题,而我本人在很长一段时间内对内存泄露产生问题认识不足。
引用计数:内部计数器和外部计数器
RIIA:是C++提供的一种对内存管理的机制,在进入作用域时执行构造,离开作用域时执行析构。这也是智能指针实现的基础。
内存区域的所有权转移与复制的问题
对内存读写的优化——写时复制。stl的string实现就是写时复制。这样讲会提高内存的IO性能,节省存储空间。
目前个人认为采用智能指针来实现对内存的控制和管理是最高效的方法,当然实现方法有很多种,在融入了引用计数器,RIIA这样的机制,并有效控制所有权的转移的问题,这样就是和内存池一样优秀的内存管理方法。
内存管理有两个方面一个是字节的,一个是对象的。在系统初始化时把对象池化在需要时申请出来,在不需要的时候返回给对象池,这样会极大的加快对象的初始化和销毁效率,并且能够避免某些内存碎片的产生,通过模板元的泛化使得对象池的支持会更加通用。而字节级别的内存池面临的问题会比较复杂,他没有办法去一次构建一个泛化的对象来管理,我们只能通过有效的搜索结构和算法来达到快速分配和回收不同数量的内存的方法。
2012年时我曾经自己实现过一个内存池,是基于字节级别的,实现模式是预分配好对应大小的内存桶,根据申请内存大小直接分配内存,而不需要切换到内核。当初这个内存管理器设计把问题复杂化了,增加了针对于不同进程数据共享的能力,这样需要添加大量的指针偏移算法,使得内存池的管理不再纯粹,这样原来的效率就丧失了很多,最后只能沦为共享内存管理器了。但是,这一实践过程,完整的实现了内存池化与分配的整个过程,后来抽离出了一个小的内存池管理器。