PHP 的垃圾回收机制基于引用计数和周期性垃圾回收两种方式。
- 引用计数
PHP 中的变量是以引用计数的方式来管理的。每当一个变量被赋予一个新的值或者被另一个变量引用时,它的引用计数就会增加;当变量不再被引用时,引用计数减少。当引用计数减少到零时,PHP 会自动释放该变量所占用的内存。 - 周期性垃圾回收
除了引用计数外,PHP 还会定期进行垃圾回收。周期性垃圾回收是为了解决引用计数无法处理的循环引用问题。例如,如果两个对象相互引用,它们的引用计数永远不会降为零,因此需要周期性垃圾回收来检测并释放这些循环引用所占用的内存。PHP 使用标记清除(Mark and Sweep)算法来处理循环引用和其他无法通过引用计数检测的情况。
解释
PHP的变量都存在一个叫 zval 的变量容器中,zval 变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。
refcount
表示一个变量当前的引用计数。引用计数是 PHP 内存管理中的一部分,用于跟踪变量的引用情况。当一个变量被赋值给另一个变量时,其引用计数会增加;当变量不再被引用时,其引用计数会减少。is_ref
一个整数类型的标志位。当 is_ref 为 1 时,表示该 zval 是一个引用,否则不是。但是,is_ref 是 zval 结构体的一个属性,而不是 is_ref 函数所表示的含义。
到这里,可以发现引用计数已可删除垃圾变量,为啥还要周期性垃圾回收呢?
因为zval容器还有复合类型(例如数组、对象),正常情况下也适用引用计数,除特殊情况
1 | $a = array( 'a' ); |
这样的话,引用计数为2,就算unset销毁 $a,引用计数还为1,尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),但引用计数不为0,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏,好消息时程序结束时会自动回收,若是程序未结束,且数量过多就会产生问题
周期性垃圾回收的使用标记清除(Mark and Sweep)算法(ChatGPT的答案)
标记清除(Mark and Sweep)算法是一种常见的内存回收算法,用于处理动态分配的内存中的垃圾。这个算法通常用于管理堆(heap)内存,其中动态分配的对象被存储。
算法主要包含两个阶段:标记阶段和清除阶段。
标记阶段
在这个阶段,算法从根对象开始遍历内存中的对象,并标记所有能够被访问到的对象。在典型的垃圾收集中,根对象通常是全局变量、调用栈中的局部变量以及静态变量等。算法会递归遍历对象引用链,标记所有可以被访问到的对象。这个阶段结束后,所有能够被访问到的对象都被标记为“活动”或“存活”,而未被标记的对象被视为“垃圾”。清除阶段
在标记阶段结束后,所有未被标记的对象都被认为是垃圾,需要被清除。在清除阶段,算法会遍历整个堆内存,释放所有未被标记的对象所占用的内存空间。这个阶段的主要目的是回收未被引用的对象所占用的内存,以便重新利用。
标记清除算法的优点是简单且高效,它可以处理循环引用等复杂情况,并且不需要暂停应用程序的执行。然而,它也有一些缺点,比如可能会产生内存碎片,导致内存分配效率降低。
总的来说,标记清除算法是一种常见且有效的垃圾回收算法,被广泛应用于许多动态语言的运行时环境中,如Python、Ruby和JavaScript等。