一、为什么需要内存池内存是一种非常宝贵的资源,需要优化访问;操作系统适合管理大块内存,比如一页(4096字节),不适合小块内存分配;不要内存池管理容易出现内存碎片,会有足够的剩余内存,但是没有连续的内存分配,会导致操作系统hold住程序进行碎片整理;另外,直接调用操作系统分配内存会导致从用户态切换到内核态,开销比较大;二、内存池设计目标:1、化零为整,减少系统调用;2.无内存泄漏;3、尽可能高效、无锁的设计;3.PHP内存池实现这个官方原理图,其中free_buckets代表一个小内存块列表,large_free_buckets代表一个大内存块列表,还有一个rest_buckets,Bird的解释是:“这个结构是双向的list,用来保存PHP分配后剩余的一些内存,避免将剩余内存无意义插入到free_buckets中造成的性能问题”。对于小块的内存,PHP也引入了缓存机制:缓存机制的引入是希望实现定位到就可以找到分配。其中free_bitmap和large_free_bitmap是位图,表示对应位对应的内存索引是否有空闲内存。下面将详细解释PHP是如何管理内存的。在解释之前,先解释一下环境。笔记实验使用的机器是64位的,PHP版本是5.6.2。以下数据均基于此前提。PHP内存管理主要围绕free_buckets和large_free_buckets这两个数组展开,这两个数组都是长度为64的数组。1.小块内存管理free_buckets管理长度小于等于528字节的内存,free_buckets[0]管理长度为16-23字节的内存,free_buckets[1]管理长度为24-31字节的内存,依此类推……它们各自都是一个双向链表,比较抽象。下面画一张图来描述内存分配和释放后的内存布局。一开始,free_buckets数组中的每一项都是NULL:返回32字节内存,返回36字节内存后,下次假设分配长度在32-39字节之间的内存,比如35,直接遍历下标2开始的元素,只要其中元素的长度大于等于要分配的长度,即返回长度为36的内存。接下来,让我们看看小块内存的分配是如何处理的。为了保证高效的内存分配,PHP每次都会从操作系统中分配大块内存。默认为256KB,可以通过环境变量ZEND_MM_SEG_SIZE设置。从操作系统分配内存后,PHP会根据之前的转换关系将内存块放入对应的内存块中,方便后续快速分配。2.大块内存管理小块内存管理长度小于等于528(参考宏ZEND_MM_MAX_SMALL_SIZE的定义)字节的内存,大于528的由large_free_buckets管理,large_free_buckets也是长度为64的数组,每个下标所管理的内存范围是前面的开闭区间,如果下标是i,那么所管理的内存的长度就是[2^i,2^(i+1))。举几个例子,large_free_buckets[9]的下标是9,2的9次方是512,所以它管理的内存长度在512-1023之间;large_free_buckets[10]管理长度在1024-2047之间的内存;large_free_buckets[11]管理长度在2048-4095之间的内存...这样一共可以管理2^64内存。当然实际中不会用到这么多,因为PHP有??内存限制相关的参数。可以看出,在大块内存的设计中,每次下标管理的内存长度差不像小块内存那样为8,而是下一个下标管理的长度是上一个下标长度的2倍管理。次;这样设计的原因是大块内存比较大,不需要管理的太细心。另外,要尽可能节省内存。如果相邻下标管理的内存长度相差8个字节,则需要一个大数组来管理这些内存。这样的设计会有问题,可能会造成内存的巨大浪费。下面的下标10管理着1024-2047之间的内存。如果你释放了一块长度为2046的内存,但只需要1030字节,那多出来的1016字节就白白浪费了。对于这个问题,PHP是通过树和双向链表来管理的:什么意思,结合释放内存的过程:1)释放2048字节的内存结合前面说的落入下标的11个元素上图中,2048的二进制数为100000000000,其中第一个1表示落在哪个下标,这里第12位从右到左排列,第11位从0开始数;从左到右第二位是0,所以放在下标为1的元素的左子树上。2.释放3100字节内存。3100的二进制值为110000011100,从左到右第二位为1,所以放在右子树上。3.释放4093字节内存4093的二进制值为111111111101,从左到右第二位为1,放到右子树上,发现右子树已经有3100,向右数,第三个digit还是1,所以在3100的右子树上申请的时候,按照这个顺序扫描。如果对应的二进制位为0,则扫描左子树,如果为1,则扫描右子树。比如此时要申请2900字节的内存,转成二进制为101101010100,从左到右第二位为1,那么到3100就返回,不会分配4093字节的内存。四。总结1、PHP的内存分配主要是围绕两个数组展开的:free_buckets和large_free_buckets,前者用来管理小块内存,后者用来管理大块内存。2、对于小块内存,尽量做到可复用,分成64段。每段管理的内存字节间隔为8,即下标0管理16-23,下标1管理24-31。以此类推...3.对于大内存,数组不宜太大,所以数组的长度也是64,但是为了不浪费内存,采用了树+双向链表的方式来管理方便快速查找,又不会浪费太多内存。4、分配内存的时候,先从操作系统中分配一大块内存,分配好后放到上面对应的数组中,方便下次使用。分布式ID算法及实现分析与实时评论系统设计在线Mysql死锁分析
