株洲网站建设服务公司,白云区网站建设公司,怎样用wordpress建站,网页传奇排行PHP中的变量是不需要手动释放的#xff0c;内核帮我们实现了变量的内训管理#xff0c;包括内存的分配与回收。本文主要介绍PHP中与内存相关的知识点#xff0c;包括变量的GC机制、垃圾回收以及底层的内存池实现#xff0c;除此还有一些线程安全相关的知识点。 变量的自动G… PHP中的变量是不需要手动释放的内核帮我们实现了变量的内训管理包括内存的分配与回收。本文主要介绍PHP中与内存相关的知识点包括变量的GC机制、垃圾回收以及底层的内存池实现除此还有一些线程安全相关的知识点。 变量的自动GC机制现代高级语言普遍提供了变量的自动GC机制由语言自己进行管理这样开发者就无需关注变量的分配与释放。PHP同样实现了这种机制通过“$”声明变量后使用完成内核会自动进行释放。 简单实现方式函数定义变量时分配一内存用于保存zval及对应的value结构在函数返回时再将内存释放若函数执行阶段该变量作为参数调用了其他函数或者赋值给了其他变量则把变量复制一份这样使得变量间相互独立。 这种方式存在的一个问题是效率低且内存浪费严重。针对这种问题提出了下列通用的解决方法。 通用实现引用写时复制。PHP变量的内存管理就是这种方式实现的。 引用当变量赋值、传递时不直接进行深度拷贝多个变量同时共用一个value引用计数来记录value被使用的变量数目写时复制当某个变量的value发生改变而无法与其他变量共用value时通过深度拷贝进行分离value。引用计数引用计数用来记录当前有多少个zval只想同一个zval_value。当有新的zval指向这个value时计数器加1当zval销毁时计数器减1。当引用计数为0时表示此value已经没有被任何变量使用这是value就可以进行释放了。 注意PHP7中将引用计数保存在了zval_value中。 写时复制写时复制在计算机系统中应用非常广泛只在必要的时候才会进行深度拷贝。换句话说资源的复制是在需要写入的时候才会进行在此之前以只读的方式共享。 回收时机变量的回收时机在自动GC机制中在zval断开value的指向时如果发现refcount0则会直接释放value这就是变量的回收时机。 除了GCPHP也可以通过unset()函数主动销毁一个变量。 垃圾回收提出的背景通过引用计数PHP实现了变量的自动GC机制但是有一种情况是这个机制无法解决的从而因变量无法回收导致内存始终得不到释放造成内存泄漏这种情况指的是循环引用。 循环引用简单来说就是变量的内部成员引用了变量自身比如数组中的某个元素指向了数组这样一来数组的引用计数中就有一个来自自身成员当所有的外部引用全部断开时数组的refcount仍然大于0而得不到释放而实际上这种变量不可能再被使用了。 垃圾由于循环引用而导致的无法释放的变量称为垃圾PHP引入垃圾回收机制来回收这种垃圾。 注意首先明确两个准则 如果一个变量value的refcount减少到0那么此value可以被释放掉不属于垃圾如果一个变量value的refcount减少之后大于0那么此value还不能被释放掉此value可能成为一个垃圾。 复合类型的回收时机在value的refcount减少之后如果仍然大于0垃圾回收器会把可能成为垃圾的value收集起来等达到一定数量后开始启动垃圾鉴定程序把真正的垃圾释放掉。 目前垃圾只会出现在array和object这两种类型中需要注意的是垃圾回收器判断是否要收集意思垃圾时并不是根据类型进行判断的而是与前面介绍的是否用到引用计数一样用过zval.u1.type_flag进行标识的只有包含IS_TYPE_COLLECTABLE标识的变量类型才会被收集。 垃圾缓存区垃圾回收器把收集的可能垃圾保存在一个buffer缓存区收集的时机是refcount减少时每次refcount减少都会触发收集动作如果已收集过就不会重复。 回收算法既然垃圾是由于成员引用自身引起的那么就对value的所有成员减一遍引用计数理解的是将现有的value的refcount减去目前的所有成员数目如果结果refcount变为0则就是表明其引用全部来自自身成员不会产生垃圾。具体步骤 步骤(1) 遍历垃圾回收器的buffer缓存区把当前value标为灰色zend_refcounted_h.gc_info置为GC_GREY然后对当前value的成员进行深度优先遍历把成员value的refcount减1并且也标为灰色。步骤(2)重复遍历buffer检查当前value引用是否为0为0则表示确实是垃圾把它标为白色GC_WHITE如果不为0则排除了引用全部来自自身成员的可能表示还有外部引用并不是垃圾这时候因为步骤(1)对成员进行了refcount减1操作需要还原回去对所有成员进行深度遍历把成员refcount加1同时标为黑色。步骤(3)再次遍历buffer将并非GC_WHIT的节点从buffer中删除最终buffer缓存区中全部为真正的垃圾最后将这些垃圾释放回收完成。 垃圾回收器主要通过zend_gc_globals这个结构对垃圾进行管理收集到的可能成为垃圾的value就保存在这个结构的buf中及垃圾缓存区。 内存池提出背景在C语言中通常使用malloc进行内存的分配而频繁地分配、释放内存无疑会产生内存碎片。在PHP中变量的分配、释放非常频繁如果所有的变量都通过malloc的方式分配将会造成严重的性能问题作为语言级的应用这种损耗是无法接受的。因此PHP实现了一套内存池Zend Memery ManagerZendMM用来替换malloc、free以解决内存频繁分配、释放问题。 内存池的作用 减少内存分配及释放的次数有效控制内存碎片的产生内存池是PHP内核中最底层内存操作它是非常独立的一个模块可以移植到其他C语言应用中去。 内存池定义了三种粒度的内存块如下 内存块Huge(chunk)Large(page)Small(slot)内存大小2MB4KB8,16,24,32···3027B30种内存分配策略RM2MB直接调用系统分配3092B RM 2044KBRM3092B 此处RM表示申请内存的大小。3092B相当于3/4个page2044KB相当于511个page。 三种粒度的内存块间的关系 一个或者若干个page可以被分割为多个slot。内存池提前定义好了30种同等大小的内存8,16,24,32···3027他们分配在不同的page上不同大小的内存可能会分配在多个连续的page申请内存时直接在对应的page上查找可用的slot。 线程安全提出背景在多线程环境中使用全局变量声明在函数之外的变量为全局变量实现多个函数间共享数据全局变量为各个线程共享不同的线程引用同一地址空间如果一个线程修改了全局变量全局变量就会影响所有的线程。 定义线程安全指的就是多线程环境下如何安全地获取公共资源。 应用场景PHP的SAPI多数是单线程环境比如Cli、Fpm和Cgi每个进程只启动一个主线程这种模式下是不存在线程安全问题的但是也存在多线程环境如Apache或用户自己嵌入的PHP实现环境这是就需要考虑线程安全了。PHP通过线程安全资源管理器(Thread Safe Resource ManageerTSRM)用于解决多线程环境下公共资源冲突问题实现线程之间安全的操作公共资源。 基本思路针对共用资源存在的问题采取各个线程各自复制同一份全局变量使用数据时各线程各取自己的副本互不干扰。其核心思想就是为不同的线程分配独立的内存空间。 基本流程如果一个资源被多个线程使用首先需要预先想TSRM注册资源TSRM会为这个资源分配一个唯一的id并把这种资源的大小、初始化函数等保存到一个tsrm_resource_type结构中各线程只能通过TSRM分配的那个id访问这个资源。 参考 秦朋 《PHP7内核剖析》第4章