>  기사  >  백엔드 개발  >  참조 카운팅이란 무엇입니까? PHP의 참조 카운팅에 대한 심층 분석!

참조 카운팅이란 무엇입니까? PHP의 참조 카운팅에 대한 심층 분석!

青灯夜游
青灯夜游앞으로
2021-07-05 19:14:552990검색

참조 카운팅이란 무엇인가요? 참조 횟수를 확인하는 방법은 무엇입니까? 참조 카운팅을 사용하는 방법은 무엇입니까? 다음 문서에서는 참조 카운팅을 이해하고 참조 카운팅을 사용하는 방법을 소개합니다.

참조 카운팅이란 무엇입니까? PHP의 참조 카운팅에 대한 심층 분석!

참조 카운팅이란 무엇입니까

PHP의 데이터 구조에서 참조 카운팅은 각 변수의 유형과 값을 저장하는 것 외에도 두 가지 추가 콘텐츠를 저장합니다. 하나는 이 변수가 참조되는지 여부입니다. , 다른 하나는 참조 횟수입니다. 왜 콘텐츠를 두 개 더 저장해야 하나요? 물론 GC(가비지 수집)용입니다.

즉, 참조 수가 0이 되면 이 변수는 더 이상 사용되지 않으며 GC를 통해 재활용하여 점유된 메모리 자원을 해제할 수 있습니다.

어떤 프로그램도 메모리 자원을 무한정 점유할 수 없습니다. 과도한 메모리 사용은 종종 심각한 문제, 즉 메모리 누수를 초래합니다. GC는 사용하지 않고도 자동으로 메모리 파괴를 완료하는 PHP의 하위 계층입니다. C. 수동으로 해제해야 합니다.

참조 횟수를 확인하는 방법은 무엇인가요?

xdebug 확장을 설치한 다음 xdebug_debug_zval() 함수를 사용하여 지정된 메모리의 자세한 정보를 확인해야 합니다. 예:

$a = "I am a String";
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=0)='I am a String'

위에서 볼 수 있듯이 이 $a 변수의 내용은 나는 문자열입니다. 그런 문자열입니다. 괄호 안의 refcount는 참조 개수이고, is_ref는 변수 참조 여부를 나타냅니다. 변수 할당을 통해 이 두 매개변수가 어떻게 변경되는지 살펴보겠습니다.

$b = $a;
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=0)='I am a String'

$b = &$a;
xdebug_debug_zval('a');
// a: (refcount=2, is_ref=1)='I am a String'

일반 할당을 수행하면 refcount와 is_ref에는 변화가 없지만 참조 할당을 수행하면 refcount가 2가 되고 is_ref가 1이 되는 것을 볼 수 있습니다. 이는 현재 a 변수가 참조로 할당되고 해당 메모리 기호 테이블이 a를 제공하고 해당 메모리 기호 테이블이

$c = &$a;
xdebug_debug_zval('a');
// a: (refcount=3, is_ref=1)='I am a String'

unset($c, $b);
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=1)='I am a String'

$b = &$a;
$c = &$a;
$b = "I am a String new";
xdebug_debug_zval('a');
// a: (refcount=3, is_ref=1)='I am a String new'

unset($a);
xdebug_debug_zval('a');
// a: no such symbol

继续增加一个 c的引用赋值,可以看到refcount会继续增加。然后unsetc 的引用赋值,可以看到 refcount 会继续增加。然后 unset 掉b 和 $c 之后,refcount 恢复到了1,不过这时需要注意的是,is_ref 依然还是1,也就是说,这个变量被引用过,这个 is_ref 就会变成1,即使引用的变量都已经 unset 掉了这个值依然不变。

最后我们 unset 掉 $a ,显示的就是 no such symbol 了。当前变量已经被销毁不是一个可以用的符号引用了。(注意,PHP中的变量对应的是内存的符号表,并不是真正的内存地址)

对象的引用计数

和普通类型的变量一样,对象变量也是使用同样的计数规则。

// 对象引用计数
class A{

}
$objA = new A();
xdebug_debug_zval('objA');
// objA: (refcount=1, is_ref=0)=class A {  }

$objB = $objA;
xdebug_debug_zval('objA');
// objA: (refcount=2, is_ref=0)=class A {  }

$objC = $objA;
xdebug_debug_zval('objA');
// objA: (refcount=3, is_ref=0)=class A {  }

unset($objB);
class C{

}
$objC = new C;
xdebug_debug_zval('objA');
// objA: (refcount=1, is_ref=0)=class A {  }

不过这里需要注意的是,对象的符号表是建立的连接,也就是说,对 objC进行重新实例化或者修改为NULL,并不会影响objC 进行重新实例化或者修改为 NULL ,并不会影响objA 的内容。对象进行普通赋值操作也是引用类型的符号表赋值,所以我们不需要加 & 符号。

数组的引用计数

// 数组引用计数
$arrA = [
    'a'=>1,
    'b'=>2,
];
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=0)=array (
//     'a' => (refcount=0, is_ref=0)=1, 
//     'b' => (refcount=0, is_ref=0)=2
// )

$arrB = $arrA;
$arrC = $arrA;
xdebug_debug_zval('arrA');
// arrA: (refcount=4, is_ref=0)=array (
//     'a' => (refcount=0, is_ref=0)=1, 
//     'b' => (refcount=0, is_ref=0)=2
// )

unset($arrB);
$arrC = ['c'=>3];
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=0)=array (
//     'a' => (refcount=0, is_ref=0)=1, 
//     'b' => (refcount=0, is_ref=0)=2
// )

// 添加一个已经存在的元素
$arrA['c'] = &$arrA['a'];
xdebug_debug_zval('arrA');
// arrA: (refcount=1, is_ref=0)=array (
//     'a' => (refcount=2, is_ref=1)=1, 
//     'b' => (refcount=0, is_ref=0)=2, 
//     'c' => (refcount=2, is_ref=1)=1
// )

调试数组的时候,我们会发现两个比较有意思的事情。

一是数组内部的每个元素又有单独的自己的引用计数。这也比较好理解,每一个数组元素都可以看做是一个单独的变量,但数组就是这堆变量的一个哈希集合。如果在对象中有成员变量的话,也是一样的效果。当数组中的某一个元素被 & 引用赋值给其他变量之后,这个元素的 refcount 会增加,不会影响整个数组的 refcount 。

二是数组默认上来的 refcount 是2。其实这是 PHP7 之后的一种新的特性,当数组定义并初始化后,会将这个数组转变成一个不可变数组(immutable array)。为了和普通数组区分开,这种数组的 refcount 是从2开始起步的。当我们修改一下这个数组中的任何元素后,这个数组就会变回普通数组,也就是 refcount 会变回1。这个大家可以自己尝试下,关于为什么要这样做的问题,官方的解释是为了效率,具体的原理可能还是需要深挖 PHP7 的源码才能知晓。

关于内存泄露需要注意的地方

其实 PHP 在底层已经帮我们做好了 GC 机制就不需要太关心变量的销毁释放问题,但是,千万要注意的是对象或数组中的元素是可以赋值为自身的,也就是说,给某个元素赋值一个自身的引用就变成了循环引用。那么这个对象就基本不太可能会被 GC 自动销毁了。

// 对象循环引用
class D{
    public $d;
}
$d = new D;
$d->d = $d;
xdebug_debug_zval('d');
// d: (refcount=2, is_ref=0)=class D { 
//     public $d = (refcount=2, is_ref=0)=... 
// }

// 数组循环引用
$arrA['arrA'] = &$arrA;
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=1)=array (
//     'a' => (refcount=0, is_ref=0)=1, 
//     'b' => (refcount=0, is_ref=0)=2, 
//     'arrA' => (refcount=2, is_ref=1)=...
// )

不管是对象还是数组,在打印调试时出现了 ... 这样的省略号,那么你的程序中就出现了循环引用。在之前的文章 关于PHP中对象复制的那点事儿 中我们也讲过这个循环引用的问题,所以这个问题应该是我们在日常开发中应该时刻关注的问题。

总结

引用计数是了解垃圾回收机制的前提条件,而且正是因为现代语言中都有一套类似的垃圾回收机制才让我们的编程变得更加容易且安全。那么有人说了,日常开发根本用不到这些呀?用不到不代表不应该去学习,就像循环引用这个问题一样,当代码中充斥着大量的类似代码时,系统崩溃只是迟早的事情,所以,这些知识是我们向更高级的程序进阶所不可或缺的内容。

测试代码:https://github.com/zhangyue0503/dev-blog/blob/master/php/202004/source/PHP%E7%9A%84%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D%EF%BC%9F.php

参考文档:
https://www.php.net/manual/zh/features.gc.refcounting-basics.php
https://www.jianshu.com/p/52450a61354d

本文转载自:https://juejin.cn/post/6950093123682828301

作者:硬核项目经理

推荐学习:《PHP视频教程

위 내용은 참조 카운팅이란 무엇입니까? PHP의 참조 카운팅에 대한 심층 분석!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제