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的指向时,如果发现refcount=0,则会直接释放value,这就是变量的回收时机。

    除了GC,PHP也可以通过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 Manager,ZendMM),用来替换malloc、free,以解决内存频繁分配、释放问题。

  • 内存池的作用

    • 减少内存分配及释放的次数
    • 有效控制内存碎片的产生

内存池是PHP内核中最底层内存操作,它是非常独立的一个模块,可以移植到其他C语言应用中去。

内存池,定义了三种粒度的内存块,如下:

内存块 Huge(chunk) Large(page) Small(slot)
内存大小 2MB 4KB 8,16,24,32···3027B(30种)
内存分配策略 RM>2MB,直接调用系统分配 3092B< RM <2044KB RM<=3092B

此处RM表示申请内存的大小。3092B相当于3/4个page,2044KB相当于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 Manageer,TSRM),用于解决多线程环境下公共资源冲突问题,实现线程之间安全的操作公共资源。

  • 基本思路:针对共用资源存在的问题,采取各个线程各自复制同一份全局变量,使用数据时各线程各取自己的副本,互不干扰。其核心思想就是为不同的线程分配独立的内存空间。

  • 基本流程:如果一个资源被多个线程使用,首先需要预先想TSRM注册资源,TSRM会为这个资源分配一个唯一的id,并把这种资源的大小、初始化函数等保存到一个tsrm_resource_type结构中,各线程只能通过TSRM分配的那个id访问这个资源。

参考: 秦朋 《PHP7内核剖析》第4章

PHP内核——内存管理相关推荐

  1. 【Linux 内核 内存管理】虚拟地址空间布局架构 ③ ( 内存描述符 mm_struct 结构体成员分析 | mmap | mm_rb | task_size | pgd | mm_users )

    文章目录 一.mm_struct 结构体成员分析 1.mmap 成员 2.mm_rb 成员 3.get_unmapped_area 函数指针 4.task_size 成员 5.pgd 成员 6.mm_ ...

  2. 【Linux 内核 内存管理】内存管理架构 ④ ( 内存分配系统调用过程 | 用户层 malloc free | 系统调用层 brk mmap | 内核层 kmalloc | 内存管理流程 )

    文章目录 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) 二.内存管理流程 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) " 堆内存 " ...

  3. 【Linux 内核 内存管理】内存管理架构 ② ( 用户空间内存管理 | malloc | ptmalloc | 内核空间内存管理 | sys_brk | sys_mmap | sys_munmap)

    文章目录 一.用户空间内存管理 ( malloc / free / ptmalloc / jemalloc / tcmalloc ) 二.内核空间内存管理 1.内核内存管理系统调用 ( sys_brk ...

  4. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  5. 【Linux 内核 内存管理】RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )

    文章目录 一.RCU 机制适用场景 二.RCU 机制特点 三.使用 RCU 机制保护链表 一.RCU 机制适用场景 在上一篇博客 [Linux 内核 内存管理]RCU 机制 ① ( RCU 机制简介 ...

  6. pae扩展内存 linux,浅析linux内核内存管理之PAE

    浅析linux内核内存管理之PAE 早期Intel处理器从80386到Pentium使用32位物理地址,理论上,这样可以访问4GB的RAM.然而,大型服务器需要大于4GB的RAM来同时运行数以千计的进 ...

  7. Linux内核内存管理(3):kmemcheck介绍

    Linux内核内存管理 kmemcheck介绍 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 5.10.13不存在kmemcheck的概念,取代的是k ...

  8. Linux内核内存管理(1):内存块 - memblock

    Linux内核内存管理 内存块 - memblock rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. 简介 内存管理是操作系统内核中最复杂的部分之 ...

  9. 【Linux 内核 内存管理】内存映射相关数据结构 ③ ( vm_area_struct 结构体成员分析 | shared 成员 | anon_vma_chain 成员 | anon_vma 成员 )

    文章目录 一.vm_area_struct 结构体成员分析 1.shared 成员 2.anon_vma_chain 成员 3.anon_vma 成员 二.vm_area_struct 结构体完整源码 ...

  10. Linux内核内存管理(2):固定映射地址(fixmap)和输入输出重映射(ioremap)

    Linux内核内存管理 固定映射地址(fixmap)和输入输出重映射(ioremap) rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. Print ke ...

最新文章

  1. git 换行问题_后端必备的 Git 分支开发规范指南
  2. 【JetPack】ViewBinding 视图绑定组件 ( 启用模块 | 视图绑定定制 | 绑定类名称生成规则 | 绑定类字段生成规则 | 绑定类获取根视图 | 绑定类获取布局组件 )
  3. 如何编写服务器文档记录
  4. 从特斯拉到计算机视觉之「图像语义分割」
  5. 基于Echarts的HTML5 Canvas折线图柱状图DEMO演示
  6. aws v2.2.exe_如何在AWS Elastic Beanstalk上部署Rails 5.2 PostgreSQL应用
  7. 记录自己灵感闪现的开发语录 每日更新 记录变强或者变得更菜的过程
  8. 私人服务器能不能微信授权,微信授权流程
  9. javaweb实训第四天下午——员工管理系统-JSPServletJDBC综合练习-CRUD
  10. 微信快速开发框架(八)-- V2.3--增加语音识别及网页获取用户信息,代码已更新至Github...
  11. 3 矩阵运算_FlyAI小课堂:小白学PyTorch(11) 常见运算详解
  12. iOS开发多线程篇---线程间的通信
  13. Android游戏编程之从零开始pdf
  14. C语言程序设计中十全十美,21世纪高校计算机应用技术系列规划教材 C语言程序设计-潭浩强主编 林小茶编著.pdf...
  15. 一文了解智能门锁的功能选购和安全性
  16. 同个网络找不到计算机打印机共享,搜索不到共享打印机怎么办_同一局域网找不到共享打印机的解决方法-系统城...
  17. SpringBoot测试:pom文件版本重复和不一致导致的问题xxxConfigurationPropertySource和Assert.state(ZLjava/util/function/Supp
  18. CAD绘制粗略的示坡线
  19. 封装一个活灵活现的原生JS排序,js按照拼音排序,js按照“数字-字符串-汉字拼音”排序,数组对象排序,数组排序微调即可
  20. MPEG音频编码原理及编码器调试

热门文章

  1. Ruby中如何识别13位的时间戳
  2. android项目闪屏页细节
  3. [高中作文赏析]相约
  4. 使用 JavaScript 实现灵活的固定导航功能
  5. 查看Mysql数据库有多大
  6. android 自定义帧动画,Android 自定义方式实现帧动画效果
  7. python中count的作用_python中函数COUNT()的功能是什么
  8. php显示网卡信息,netwox显示网络配置信息
  9. [蓝桥杯][历届试题]小朋友排队(树状数组)
  10. Equalize the Remainders(set二分+思维)