PHP 内存中的对象和引用简介
已发表: 2022-03-11为了更好地理解 PHP 如何管理内存中的变量和对象,我在学习 PHP 认证时首先起草了这篇文章。 经过大量研究,我意识到要找到我的问题的答案并不容易,所以一旦我完成了,我决定将这些信息记录下来,以便人们可以在一个地方找到所有信息。
在本文中,我将讨论如何在内存中控制对象和变量引用,因为这是一个可以引发讨论和不同意见的问题。 需要思考的一个问题是:“默认情况下,对象在 PHP 中是通过引用传递还是通过复制传递?” 我将首先讨论 PHP 中没有哪些引用; 其次,我将讨论它们是什么,最后,我将研究垃圾收集器在 PHP 中是如何工作的。
执行$a = new Foo();
类的语句时,PHP 如何在内存中创建对象? 如今,内存不再像过去那样昂贵且资源有限。 但是,对于优秀的 PHP 开发人员来说,了解和理解在执行应用程序期间如何在内部管理变量和对象仍然很重要。
PHP 中的对象和引用
许多人说——在 PHP 书籍和网上——默认情况下 PHP 中的对象是通过引用传递的。 其他人说 PHP 中的对象是通过副本分配的。 要弄清楚哪个陈述是正确的,首先我们必须分析 PHP 中什么是(什么不是)引用。
什么不是PHP 中的引用?
比知道 PHP 中的引用是什么更重要的是知道它们不是什么。 在 PHP 中,引用不是 C 风格的指针; 您不能像使用 C 指针那样对引用进行算术运算。 为什么? 因为,与 C 不同,PHP 引用并不是真正的内存地址,因为它们不是表示内存位置的数字。 但是,什么是参考?
PHP 中的引用是什么?
在 PHP 中,引用是允许两个不同变量读取和写入单个值的“别名”。 换句话说,它们是允许从具有不同名称的变量访问相同值的机制,因此它们的行为就像它们是同一个变量一样。 请记住,在 PHP 中,变量名和变量的内容是两个完全不同的东西,在所谓的“符号表”中链接。 因此,当我们创建一个引用时,它只是在符号表中为该变量添加一个别名。 假设我们有以下代码:
$a = new Foo();
执行上述语句时,在内存中创建变量$a
,在内存中创建Foo
类型的对象,并在符号表中添加一个条目,表明变量$a
“引用”(或与, 或指向, 或任何你想调用的对象) Foo
对象, 但它本身并不是指向该对象的指针。 从概念上讲,我们有这样的插图:
小测验:如果我们执行这个会发生什么?
$b = $a;
并不是$b
成为$a
a 的引用; 我们也不能说$b
是$a
a 的副本。 真正发生的是,我们在内存中创建了一个新变量$b
,然后在符号表中添加了一个新条目,表明变量$b
也引用了与$a
a 相同的Foo
对象。 因此,从视觉上看,我们有一些类似于此插图中显示的内容:
现在,如果我们执行:
$c = &$a;
我们将在内存中创建第三个变量$c
,但不会在符号表中为$c
创建新条目。 相反,在符号表中,记录了$c
是$a
a 的别名,因此它的行为相同,但$c
不是指向$a
的指针——这与 C 中不同,它创建了称为指针指针的东西。 为了形象化,我们有一些类似于下图所示的东西:
一旦我们想要修改这三个变量中的任何一个的值(即写入一个新值),PHP 就必须在内存中创建一个新的z_val
结构来分隔变量$b
的内容和对$a
/ $c
因此它们可以各自独立修改而不会影响对方的价值。 因此,如果我们在前面的脚本中添加以下行:
$b = new Bar();
在记忆中,我们会遇到如下图所示的情况:

现在,让我们考虑一个更完整的例子:
<?php class myClass { public $var; function __construct() { $this->var = 1; } function inc() { return ++$this->var; } } $a = new myClass(); // $a "references" a Foo object $b = $a; //b also references the same Foo object as a //($a) == ($b) == <id> of Foo object, but a and b are different entries in symbols table echo "$a = ";var_dump($a); echo "$b = ";var_dump($b); $c = &$a; //$c is an alias of $a //($a, $c) == <id> of Foo object, c is an alias of a in the symbols table echo "$c = ";var_dump($c); $a = NULL; //The entry in the symbols table which links "$a" with Foo object is removed //Since that entry was removed, $c is not related to Foo anymore //Anyway, Foo still exists in memory and it is still linked by $b echo "$a = ";var_dump($a); echo "$b = ";var_dump($b); echo "$c = ";var_dump($c); echo "$b->var: ".$b->inc(); echo "$b->var: ".$b->inc(); $b = NULL; //The entry in the symbols table which links "$b" with the Foo object is removed //There are no more entries in the symbols table linked to Foo, //So, Foo is not referenced anymore and can be deleted by the garbage collector echo "$b = ";var_dump($b);
执行上述脚本产生的输出是:
$a = object(myClass)#1 (1) { ["var"]=> int(1) } $b = object(myClass)#1 (1) { ["var"]=> int(1) } $c = object(myClass)#1 (1) { ["var"]=> int(1) } $a = NULL $b = object(myClass)#1 (1) { ["var"]=> int(1) } $c = NULL $b->var: 2 $b->var: 3 $b = NULL
PHP 垃圾回收
最后,让我们看看 PHP 垃圾回收是如何工作的,因为它是在 5.3 版本中引入的。 当符号表中没有对该对象的引用时,PHP 垃圾收集器将删除 PHP 内存中的对象或变量。 也就是说,PHP 从对象创建之时起就维护一个对象的引用计数器,以便在 PHP 脚本执行期间,计数器根据“指向”它的变量来递增和递减引用计数器。 一旦引用计数达到 0(即,没有引用该对象,因此它没有被使用),PHP 将该对象标记为可移除,以便在 PHP 垃圾收集器的下一次传递中,它将从内存中移除,释放该空间以供重用。 如果您想更深入地了解 PHP 垃圾回收的工作原理,请阅读此文档。
结束的想法
我希望我已经澄清了一点,PHP 如何处理内存中的对象和变量,以及它如何“选择”应该由 PHP 垃圾收集器删除的对象。
既然您了解了 PHP 如何在内部管理内存中的变量和对象,请拿起您的笔记本电脑并开始尝试一些代码来证明您所学的知识。 尝试使用变量和引用。 此外,尝试更改变量的值如何影响引用它的另一个变量的值。 给你一个问题:执行下面的代码后, $a
和$b
的值是多少?
$a = '1'; $b = &$a; $b = "2$b";
如果您有兴趣阅读有关 PHP 性能特性的更多信息,请查看 Toptaler Vilson Duka 的这篇文章。