起因

下面这段代码执行后,内存有增无减,增加了200M,iOS平台200M不能接受了

// STL 集合类
void test1() {
list<int> mList;
for (int i=0; i<1000000; i++) {
mList.push_back(i);
}
mList.clear();
}
// mList 作用域 {} 内,stack 上的变量由编译器出了 } 自动释放

STL 底层是用 new/delete 分配内存的,new/delete 是基于 malloc/free 分配的,malloc/free 又是基于各个操作系统统一封装,于是我写了下面的测试代码

// 申请 100w 个 100byte 的空间,再释放掉
void test2() {
char **ptr = (char **)malloc(1000000 * sizeof(char *));
for (int i=0; i<1000000; i++) {
*(ptr + i) = (char *)malloc(100 * sizeof(char));
}
for (int i=0; i<1000000; i++) {
free(*(ptr + i));
}
free(ptr);
}

内存依然去到了300M,无减少。 原因:

  • Windows 平台调用free,内存会马上降回来。
  • Linux 平台调用free,内存不会释放回OS,而是释放回系统的内存缓冲池,进程退出时才释放回OS。(ps:Linux下谷歌有个 tcmalloc 能做到立刻释放到OS,或者malloc_trim(0))
  • iOS 平台调用 free 后,也只是释放到系统的内存缓冲池里,进程退出才释放回OS。

这就很麻烦了,iOS平台不能去到太高的内存,不然进程会被kiil的,必须要手动释放。
查了一下,iOS用不了谷歌的tcmalloc,malloc_trim也用不了,只能用 malloc_zone_t 自定义一个缓存区,用完自己销毁,就能够释放内存。如下面这段代码,就能把内存释放会 OS 了。

// 自定义 malloc_zone_t 内申请/释放内存
void test3() {
malloc_zone_t *my_zone = malloc_create_zone(0, 0); // 创建一个 zone
char **ptr = (char **)my_zone->malloc(my_zone, 1000000 * sizeof(char *));
for (int i=0; i<1000000; i++) {
*(ptr + i) = (char *)my_zone->malloc(my_zone, 104 * sizeof(char));
}
for (int i=0; i<1000000; i++) {
my_zone->free(my_zone, *(ptr + i));
}
my_zone->free(my_zone, ptr); // 内存释放回 zone,并不是释放会OS,内存还是占用着
malloc_destroy_zone(my_zone); // 内存释放回 os 层,内存占用减少
}

现在能搞定的是通过创建 zone 内的 malloc/free 能控制内存释放回 OS,又有一个问题来了,STL C++的类不能用 malloc/free,内存依然不能释放会OS,这个问题还在找办法,有人知道麻烦告诉我,thanks。


原理

操作系统管理内存的方式进化:段式 -> 页式 -> 段页式。
段式有内存外部碎片,内存利用率低,于是发明了页式,页式有内部碎片。同时页式管理中,进程不一定要全部在内存当中了,不在的部分是虚拟内存,进程的物理空间也不一定连续,虚拟地址通过页表能算出物理地址,不在内存中产生缺页中断。。。等等

页式管理中,一个页为4KB,只能按页为单位拿内存。假设一个对象只有100 byte,为一个对象分配一个页的内存,有3KB多是浪费的,浪费的部分叫做内部碎片。一个进程有几十万个对象,碎片就非常多了利用率低。
于是发明内存缓冲池,假如申请的内存大于一个页的4KB,那么去OS申请。
假如申请的内存都是很小的,几百字节以内的,那么在缓冲池内申请。释放的时候,只释放回缓冲池,预防下次还要申请。也就是从OS拿部分内存自己进程持有,由自己负责分配。
缓冲池内存管理算法:空闲链表,隐式的空闲链表,位图。

经过上面的代码测试,
默认缓冲池直接malloc/free,速度 70ms,但内存单调增长。
创建和销毁缓冲池,自己的缓冲池内malloc/free,速度 160ms,但内存不增长。
所以缓冲池的出现体现了时间换空间,空间换时间的计算思维。
缓冲池的好处:
1.不用触发系统调用,速度快,2.减少内存碎片,提高利用率。
如果不适当释放内存,容易导致内存单调增长。

 
 

iOS 的 OC 对象都是通过 alloc 方法创建的,alloc 方法调用了 allocWithZone,这个 NSZone 底层就是 malloc_zone_t,就是缓冲池。所以给对象分配内存的速度是很快的。

 
 

内存管理的一般方法

  1. C 风格的内存管理程序主要实现 malloc()和 free()函数。
  2. 内存池是一种半内存管理方法。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己 的内存池。在结束每个阶段时,会一次释放所有内存。
  3. 引用计数
  4. 垃圾收集。
    垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集 器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的 一组“基本”数据——栈数据、全局变量、寄存器——作为出发点。然后它们尝试去追踪通 过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可 以被销毁并重新使用这些无用的数据。

Glibc存在的某个问题

Glibc 在内存回收方面做得不太好,常见的一个问题,申请很多内存,然后又释放,只是有一小块 没释放,这时候 Glibc 就必须要等待这一小块也释放了,也把整个大块释放,极端情况下, 可能会造成几个 G 的浪费。

内存管理数据结构概述

Main_arena 与 non_main_arena

每个进程只有一个主分配区,但可能存在多个非主分配区,ptmalloc 根据系统对分配区 的争用情况动态增加非主分配区的数量,分配区的数量一旦增加,就不会再减少了。

主分配区可以访问进程的 heap 区域和 mmap 映射区域,也就是说主分配区可以使用 sbrk 和 mmap 向操作系统申请虚拟内存。非主分配区只能使用mmap向操作系统申请虚拟内存。

而且程序线程很多的情况下,锁等待的时间就会 延长,导致 malloc 性能下降。一次加锁操作需要消耗 100ns 左右,正是锁的缘故,导致 ptmalloc 在多线程竞争情况下性能远远落后于 tcmalloc。

chunk的组织

不管内存是在哪里被分配的,用什么方法分配,用户请求分配的空间在 ptmalloc 中都使 用一个 chunk 来表示。用户调用 free()函数释放掉的内存也并不是立即就归还给操作系统, 相反,它们也会被表示为一个 chunk,ptmalloc 使用特定的数据结构来管理这些空闲的 chunk。
ptmalloc 在给用户分配的空间的前后加上了一些控制信息,用这样的方法来记录分配的
信息,以便完成分配和释放工作。

空闲 chunk 容器

用户 free 掉的内存并不是都会马上归还给系统,ptmalloc 会统一管理 heap 和 mmap 映
射区域中的空闲的 chunk,当用户进行下一次分配请求时,ptmalloc 会首先试图在空闲的 chunk 中挑选一块给用户,这样就避免了频繁的系统调用,降低了内存分配的开销。ptmalloc 将相似大小的 chunk 用双向链表链接起来,这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了 128 个 bin,并使用一个数组来存储这些bin

  1. 数组中的第一个为 unsorted bin
  2. 数组中从编号2到编号为64的bin 称为 small bins,同 一个 small bin 中的 chunk 具有相同的大小。相邻的small bins中的chunk大小相差为8B。
  3. Small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 分别包含了一个给定范围 内的 chunk,其中的 chunk 按大小序排列。相同大小的 chunk 同样按照最近使用顺序排列。

Fast Bins

ptmalloc 中在分配过程中 引入了 fast bins,不大于 max_fast(默认值为 64B)的 chunk 被释放后,首先会被放到 fast bins 中

Unsorted Bin

unsorted bin 的队列使用 bins 数组的第一个,如果被用户释放的 chunk 大于 max_fast,或者 fast bins 中的空闲 chunk 合并后,这些 chunk 首先会被放到 unsorted bin 队列中。在进 行 malloc 操作的时候,如果在 fast bins 中没有找到合适的 chunk,则 ptmalloc 会先在 unsorted bin 中查找合适的空闲 chunk,然后才查找 bins。
如果 unsorted bin 不能满足分配要求。malloc 便会将 unsorted bin 中的 chunk 加入 bins 中。然后再从 bins 中继续进行查找和分配过程。

从这个过程可以看出来,unsorted bin可以看做是bins的一个缓冲区,增加它只是为了加快分配的速度。

Top chunk

top chunk对于主分配区和非主分配区是不一样的。

  1. 非主分配区

对于非主分配区会预先从 mmap 区域分配一块较大的空闲内存模拟 sub-heap, 通过管
理 sub-heap 来响应用户的需求,因为内存是按地址从低向高进行分配的,在空闲内存的最高处, 必然存在着一块空闲 chunk, 叫做 top chunk。当bins和fast bin都满足不了用户的需求,ptmalloc会从top chunk分出一块内存给用户,如果top chunk空间不足,会重新分配一个sub-heap,将top chunk迁移到行的sub-heap上。新的sub-heap和旧的sub。在分配过程中,top chunk的大小随着切割动态变化。

  1. 主分配区

主分配区是唯一能够映射进程 heap 区域的分配区,它可以通过 sbrk()来增大或是
收缩进程 heap 的大小。top chunk在heap的最上面,如果申请内存时,top chunk空间不足,ptmalloc
会调用 sbrk()将的进程 heap 的边界 brk 上移,然后修改 top chunk 的大小。

mmaped chunk

当分配的内存空间过大的时候,top chunk也不能满足需求。ptmalloc会直接调用mmap给用户分配内存。

mmap分配阈值( mmap threshold,默认值为 128KB,分配阈值可以动态调整。如果开启了 mmap 分配
阈值的动态调整机制,并且当前回收的 chunk 大小大于 mmap 分配阈值,将 mmap
分配阈值设置为该 chunk 的大小,将 mmap 收缩阈值设定为 mmap 分配阈值的 2
倍。

last remainder

last remainder也不存在于bins中,当用户在small bins中找不到合适的chunk,如果last remainder的大小大于small chunk的大小,last remainder会分裂为两个chunk,一个返回给用户,另一个变成新的remainder chunk。

内存分配概述

以32位系统为例

  • 小于等于 64 字节:用 pool 算法分配。
  • 64 到 512 字节之间:在最佳匹配算法分配和 pool 算法分配中取一种合适的。
  • 大于等于 512 字节:用最佳匹配算法分配。
  • 大于等于 mmap 分配阈值 (默认值 128KB): 根据设置的 mmap 的分配策略进行分配,
    如果没有开启 mmap 分配阈值的动态调整机制,大于等于 128KB 就直接调用 mmap20
    分配。 否则,大于等于 mmap 分配阈值时才直接调用 mmap()分配。

ptmalloc 的响应用户内存分配要求的具体步骤

  1. 获取一个未加锁的分配区,如果所有分配区都加了锁,ptmalloc会开辟一个新的分配区。开辟新分配区时,会调用mmap创建一个sub-heap,并设置好top chunk。
  2. 将用户的请求大小转换为实际需要分配的 chunk 空间大小
  3. 判断所需分配 chunk的大小是否满足 chunk_size <= max_fast (max_fast 默认为 64B),如果是的话, 则转下一步, 否则跳到第 5 步。
  4. 首先尝试在 fast bins 中取一个所需大小的 chunk 分配给用户。 如果可以找到, 则分配结束。 否则转到下一步。
  5. 判断所需大小是否处在 small bins 中, 即判断 chunk_size < 512B 是否成立。 如果chunk大小处在small bins中,则转下一步,否则转到第7步。
  6. 根据所需分配的 chunk 的大小, 找到具体所在的某个 small bin, 从该 bin 的尾部摘取一个恰好满足大小的chunk。若成功,则分配结束,否则,转到下一步。
  7. 首先尽可能将fast bins中的chunk合并,并且放入unsorted bin中。如果unsorted bin中只有一个chunk,而且在上次分配的时候使用过,并且说需要分配的属于 small bins,并且 chunk 的大小大于等于需要分配的大小,这种情况下就直
    接将该 chunk 进行切割,分配结束。否则,将unsorted bin中的chunk放入small bins或者large bins。进入下一步。
  8. 从large bins分配一块合适的chunk.
  9. 根据申请空间的大小和mmap分配阈值判断,从top chunk中分配内存还是直接调用mmap分配内存。

总结:

小内存: [获取分配区(arena)并加锁] -> fast bins -> small bins -> 合并fast bins加入unsorted bins -> unsorted bins合并,加入small bins或者large bins -> small bins -> large bins -> top chunk(低于mmap阈值) -> mmap(高于mmap 阈值)

大内存: 直接mmap

内存回收概述

  1. 根据地址对齐找到sub-heap,从sub-heap头部信息找到属于哪个分配区,获取分配区的锁,保证线程安全。
  2. 判断所需释放的 chunk 是否为 mmaped chunk,如果是,则调用munmap()释放mmaped chunk,解除内存空间映射,该该空间不再有效。
  3. 判断 chunk 的大小和所处的位置,若 chunk_size <= max_fast,并且 chunk 并不位于heap 的顶部,也就是说并不与 top chunk 相邻,则转到下一步。否则跳到5。
  4. 将 chunk 放到 fast bins 中, chunk 放入到 fast bins 中时,并不会修改chunk的使用状态位,也并不尝试合并。然后free函数返回。
  5. 判断前一个 chunk 是否处在使用中,如果前一个块也是空闲块,则合并。并转下一步。
  6. 判断当前释放 chunk 的下一个块是否为 top thunk,如果是,则转第8步,否则转下一步。
  7. 判断下一个 chunk 是否处在使用中, 如果下一个 chunk 也是空闲的, 则合并, 并将合并后的 chunk 放到 unsorted bin 中。
  8. 将chunk与top chunk合并。
  9. 判断合并后的chunk的大小是否大于FASTBIN_CONSOLIDATION_THRESHOLD(默认
    64KB), 如果是的话,则会触发进行 fast bins 的合并操作, fast bins 中的 chunk 将被遍历,并与相邻的空闲 chunk 进行合并,合并后的 chunk 会被放到 unsorted bin 中。fast bins 将变为空, 操作完成之后转下一步。
  10. 判断top chunk是否大于mmap收缩值,如果大于就将一部分top chunk归还给操作系统。

总结:

  1. 大内存:直接munmap
  2. 小内存(与top chunk相邻):如果在top chunk上面,尽可能合并chunk,然后与top chunk合并。
  3. 小内存(chunk_size <= max_fast):直接放入fast_bin
  4. 小内存(chunk_size > max_fast):与周围的chunk合并后放到unsorted bin中。
  5. (all)如果合并后的chunk触发合并fast bin操作,合并fast bin放到unsorted中。

ptmalloc缺点

  1. 因为 ptmalloc 收缩内存是从top chunk开始,如果与top chunk相邻的chunk不能释放,top chunk以下都不能释放。对于长期持有的内存,尽量直接越过mmap阈值调用mmap直接分配
  2. 多线程不友好,频繁加锁。

free()后内存不释放问题 - 内存缓冲池技术(转)的更多相关文章

  1. Centos清理内存 内存回收释放及内存使用查看的相关命令

    在清理前内存使用情况 free -m 用以下命令清理内存 echo 1 > /proc/sys/vm/drop_caches 清理后内存使用情况再用以下命令看看. free –m 多出很多内存了 ...

  2. delphi 精要-读书笔记(内存分配释放)

    delphi 精要-读书笔记(内存分配释放)     1.内存分为三个区域:全局变量区,栈区,堆区 全局变量区:专门存放全局变量 栈区:分配在栈上的变量可被栈管理器自动释放 堆区:堆上的变量内存必须人 ...

  3. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  4. linux内存源码分析 - 内存池

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的 ...

  5. java 垃圾回收(堆内存)、以及栈内存的释放

    一.Java的垃圾回收机制———解疑 Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间. 需要注意的是:垃圾回收回收的是无任何引用的 ...

  6. 解决IE浏览器IFrame对象内存不释放问题

    最近项目组发现在使用showModalDialog弹出窗体中如果包含IFrame对象,则IFrame对象占用的内存资源在窗体关闭后不会释放.弹出关闭反复多次后,IE浏览器内存占用可超过数百M,严重时I ...

  7. 内存管理概述、内存分配与释放、地址映射机制(mm_struct, vm_area_struct)、malloc/free 的实现

    http://blog.csdn.net/pi9nc/article/details/23334659 注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料 ...

  8. 手工释放Linux内存

    转载自:http://blog.csdn.net/wyzxg/article/details/7279986/ linux的内存查看: [root@localhost 0.1.0]# free -m  ...

  9. malloc内存分配与free内存释放的原理

    malloc内存分配与free内存释放的原理 前段时间一直想看malloc的原理,在搜了好几篇malloc源码后遂放弃,晦涩难懂. 后来室友买了本深入理解计算机系统的书,原来上面有讲malloc的原理 ...

随机推荐

  1. linux常用命令(centos)

    linux 命令有很多,常用的很少. #######################系统相关############################ lsb_release -a 查看系统信息 cat ...

  2. Excel种的数据类型研究【原创】【精】

    因为要做一个项目,开始研究Excel种的数据类型.发现偌大的一个cnblogs竟然没人写这个,自己研究以后记录下来. 在我们通常的认识中,Excel中的数据类型有这么几种 1.常规:2.数值:3.货币 ...

  3. java8学习之Predicate深入剖析与函数式编程本质

    上次[http://www.cnblogs.com/webor2006/p/8214596.html]对Predicate函数接口进行了初步的学习,其中提到了在未来要学习的Stream中得到了大量的应 ...

  4. 箭头函数中可改变this作用域,回调函数用箭头函数this指向page,自定义事件用箭头函数this指向undefined

    1.回调函数中,用箭头函数改变this的作用域 success: (res)=>{ this.setData({ //此时,this指向page页面 ... }) } 2.自定义事件中,如果使用 ...

  5. CSS3 animation属性 实现转动效果

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  6. php+大文件管理

    用过浏览器的开发人员都对大文件上传与下载比较困扰,之前遇到了一个php文件夹上传下载的问题,无奈之下自己开发了一套文件上传控件,在这里分享一下.希望能对你有所帮助.此控件PC全平台支持包括mac,li ...

  7. luogu 5561 [Celeste-B]Mirror Magic 后缀数组+RMQ+multiset

    思路肯定是没有问题,但是不知道为啥一直 TLE 两个点~ #include <bits/stdc++.h> #define N 2000006 #define setIO(s) freop ...

  8. 51 Nod 1352 集合计数

    大致题意:求ax+by=n+1的正数解的个数. 先看下面: 相信看过了通解的参数表示后已经知道怎么解了,贴代码: #include <bits/stdc++.h> #define ll l ...

  9. 51 Nod 1402 最大值

    1402 最大值  题目来源: TopCoder 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题  收藏  关注 一个N长的数组s[](注意这里的数组初始下标设为1 ...

  10. Dijkstra算法和Floyd算法的正确性证明

    说明: 本文仅提供关于两个算法的正确性的证明,不涉及对算法的过程描述和实现细节 本人算法菜鸟一枚,提供的证明仅是自己的思路,不保证正确,仅供参考,若有错误,欢迎拍砖指正   ------------- ...