最近对内存管理接触多些,就稍微研究了一下。主要分为两个部分,一是动态内存分配,二是垃圾回收。以下是对理论部分的简要回顾:
【动态内存分配】
1、uClibc的内存分配
uClibc的内存分配是在堆上进行的,采用空闲链表进行管理,对内存申请使用最先适应(first fit)算法。如果堆大小不够,则调用sbrk/mmap系统调用向系统申请扩展堆空间;如果有大量空间被释放,则调用mumap缩小堆空间。个人以为频繁调用malloc/free的开销主要在系统调用上面。
2、Linux内核的内存分配
Linux内核中,大块内存是采用分页管理,每个页面大小为4K,对内存申请使用
伙伴算法;小块内存采用
对象缓冲池方式,建立一系列不同大小的缓存对象缓冲池,对常用的对象建立该对象大小的缓冲池,对小块内存申请建立2的幂数大小的缓冲池,在缓冲池中进行内存分配。
3、Python的内存分配
与Linux内核的实现方式类似,Python也对大内存和小内存分别实行不同的分配方式。其中大内存直接调用malloc进行分配,而小内存则使用缓冲池方式。在缓冲池中包括block、pool和arena三个层次:block为内存分配的基本单位;pool为block的集合,一般大小为4K,一个pool只能包含一种大小的block;arena为pool的集合,默认大小为256K。
【垃圾回收】
1、
引用计数(reference counting)
引用计数是最经典的垃圾回收算法,也是Python正在使用的垃圾回收算法。主要思想是在对象中加入一个域进行引用计数,新建对象的引用计数为1,以后每有对象指向它时就把计数加1。当引用计数为0时,即可认为该对象为垃圾,可以进行垃圾回收。引用计数的优点是简单,且把垃圾回收的成本平均在运行时间,缺点是存在循环引用问题。
2、标记清除(mark-sweep)
标记清除是从根对象(包含全局对象和当前栈中的活动对象)出发,利用广度遍历找出所以可达的对象(即根对象直接或间接引用的对象)并进行标记,其它所有未标记的对象为不可达对象,也即垃圾,可以进行垃圾回收。该算法成功解决了循环引用问题,但因为垃圾回收动作比较集中,容易对实时性要求比较高的系统产生性能影响。
3、复制回收(copying collection)
复制回收是在系统中建立两个堆,同时只有一个堆是使用的,另一个堆用于在垃圾回收时把可达的对象复制进去。该算法的优点是可避免内存碎片,但因为需要同时存在两个堆,所以内存浪费比较大。
4、分代收集(generation collection)
该算法的主要思想是大部分的新对象生命周期比较短,而存活时间比较久的对象是垃圾的可能性比较小。所以,它会把对象分为几代,新创建的对象在最年轻一代,如果经历几次垃圾收集后该对象依然存在,则把它移到上一代中。回收时,一般只回收最年轻的一代。该算法的优点是效率比较高,且可以对不同的代使用不同的垃圾回收算法,但由于本质上它是一种以空间换时间的算法,所以存在着内存浪费。