서문:
블로그를 읽는 대부분의 프로그래머는 한자가 많이 포함된 기사를 읽는 것을 좋아하지 않을 수 있지만, 이 기사에서는 중국어를 기반으로 설명을 소개합니다. , 인내심을 가지고 읽으면 대부분의 사람들에게 확실히 보람이 있을 것입니다!
PHP는 약한 유형의 동적 스크립팅 언어라는 사실을 알고 계실 수도 있고 모르실 수도 있습니다. 소위 약한 유형은 PHP가 변수 유형을 엄격하게 확인하지 않는다는 것을 의미합니다(엄격히 말하면 PHP는 중간 수준의 강력한 유형의 언어입니다). 변수를 선언할 때 저장하는 데이터 유형을 명시적으로 표시할 필요가 없습니다. 예: $a = 1;(정수) $a ="1";(문자열)
저는 항상 PHP를 사용해 왔습니다. 그런데 PHP가 정확히 무엇이며, PHP를 그렇게 편리하고 빠른 약한 유형의 언어로 만들기 위한 기본 구현은 어떻게 달성됩니까?
최근에 많은 책과 관련 블로그 정보를 읽으며 PHP의 메커니즘에 대해 많이 배웠습니다. 핵심.
PHP에 대한 간단한 이해는 C 언어 클래스 라이브러리입니다. php.net에 가서 소스 코드를 다운로드하면 우선 핵심이 되는 것을 알 수 있습니다. PHP의 Zend 엔진은 C 언어로 작성된 함수 라이브러리로 기본 함수 관리, 메모리 관리, 클래스 관리 및 변수 관리를 처리하는 데 사용됩니다. 커널에서는 많은 확장 기능을 작성했으며 대부분은 독립적입니다. 운영 체제 비유를 사용하려면 zend
엔진은 운영 체제이며 공식적으로 많은 "애플리케이션"을 제공하지만 이 "애플리케이션"은 미디어 플레이가 아니라 mysql, libxml, dom입니다. 물론, zend 엔진의 API를 기반으로 자신만의 확장 기능을 개발할 수도 있습니다.
커널에 PHP 변수 저장 메커니즘을 도입하는 것부터 시작하겠습니다.
PHP는 입력된 언어입니다. 즉, PHP 변수는 모든 데이터 유형을 저장할 수 있습니다. 그런데 PHP는 C언어로 작성되어 있고, C언어는 강타입 언어죠? ? 모든 데이터 유형을 변수에 저장하려면? 아래의 저장 구조를 참조하세요.
Zend/zend.h 헤더 파일을 열면 다음 구조를 찾을 수 있습니다. Zval:
typedef struct _zval_struct zval;rrree
아아앙
2.zend_uchar
유형
PHP의 변수에는 4개의 스칼라 유형 (bool, int, float, string), 2개의 A 복합이 포함됩니다. (배열, 객체) 및 두 가지 특수 유형 (리소스 및 NULL). zend 내에서 이러한 유형은 다음 매크로에 해당합니다(코드 위치 phpsrc/Zend/zend.h) Zend는 유형 값에 따라 액세스할 값의 멤버를 결정합니다.
3.zend_uint refcount__gc
이 값은 실제로 기호 테이블에 존재하는 변수(또는 기호, 기호, 모든 기호의 수를 저장하는 카운터입니다. (기호 테이블), 서로 다른 범위는 서로 다른 기호 테이블을 사용합니다. 이에 대해서는 나중에 논의하겠습니다)는 zval을 가리킵니다. 변수가 생성되면 refcount=1입니다. $a = $b와 같은 일반적인 할당 작업은 zval의 refcount를 1씩 늘리고 unset 작업은 이에 따라 1씩 감소합니다. PHP5.3 이전에는 참조 계산 메커니즘을 사용하여 GC를 구현했습니다. zval의 참조 횟수가 0보다 작으면 Zend 엔진은 zval을 가리키는 변수가 없다고 생각하여 GC가 차지하는 메모리 공간을 해제했습니다. zval. 그러나 때로는 상황이 그렇게 간단하지 않습니다. 우리는 나중에 간단한 참조 카운팅 메커니즘이 순환 참조의 zval을 GC할 수 없다는 것을 알게 될 것입니다(자세한 내용은 아래 예제 3 참조). zval에 대한 설정이 해제되어 메모리 누수가 발생했습니다(메모리 누출).
4.is_ref__gc
.
이 필드는 변수가 참조 변수인지 여부를 표시하는 데 사용됩니다. . 일반 변수의 경우 값은 0이고 참조 변수의 경우 값은 1입니다. 이 변수는 zval의 공유, 분리 등에 영향을 미칩니다. 이에 대해서는 나중에 논의하겠습니다.
이름에서 알 수 있듯이 ref_count__gc 및 is_ref__gc는 PHP의 GC 메커니즘에 필요한 두 가지 매우 중요한 필드입니다. 이 두 필드의 값은 xdebug 등을 통해 디버깅할 수 있습니다. 도구를 봅니다.
다음으로 zval을 중심으로 PHP 변수의 저장 메커니즘이 무엇인지 설명하겠습니다.
이전 PHPstorm Xdebug 디버깅에서도 Xdebug 설치를 소개했습니다. 여기서는 자세히 설명하지 않겠습니다. phpstorm+Xdebug 중단점 디버깅 PHP
安装成功后,你的脚本中,可以通过xdebug_debug_zval打印Zval的信息,用法:
$var = 1; debug_zval_dump($var); $var_dup = $var; debug_zval_dump($var);
$a = 1; $b = $a; $c = $b; $d = &$c; // 在一堆非引用赋值中,插入一个引用
整个过程图示如下:
---------------------------------------------------------
$a = 1; $b = &$a; $c = &$b; $d = $c; // 在一堆引用赋值中,插入一个非引用
整个过程图示如下:
通过实例一、二,展现了,这就是PHP的copy on write写时分离机制、change
on write写时改变机制
过程:
PHP在修改一个变量以前,会首先查看这个变量的refcount,如果refcount大于1,PHP就会执行一个分离的例程,
对于上面的实例一代码,当执行到第四行的时候,PHP发现$c指向的zval的refcount大于1,那么PHP就会复制一个新的zval出来,将原zval的refcount减1,并修改symbol_table,使得$a,$b和$c分离(Separation)。这个机制就是所谓的copy on write(写时复制/写时分离)。把$d指向的新zval的is_ref的值 == 1 ,这个机制叫做change on write(写时改变)
结论:
分离指的是:分离两个变量存储的zval的位置,让分开不指向同一个空间! (那如何判定是否要分离呢,依据是什么?见下边)
改变指的是,有&引用赋值时,要把新开辟的zval 的 is_ref 赋值为1
判定是否分离的条件:如果is_ref =1 或recount == 1,则不分离
if((*val)->is_ref || (*val)->refcount<2){ //不执行Separation ... ;//process }
---------------------------------------------------------------------------------------------------
举例:
$a = $array('one'); $a[] = &$a; xdebug_debug_zval('a');
debug_zval_dump打印出zval的结构是:
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )
上述输出中,…表示指向原始数组,因而这是一个循环的引用。如下图所示:
现在,我们对$a执行unset操作,这会在symbol table中删除相应的symbol,同时,zval的refcount减1(之前为2),也就是说,现在的zval应该是这样的结构:
unset($a); (refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
(应该ref_count=1)
(unset,其实就是打断$a在 符号表(symble table) 与zval 的一个指针映射关系。)
这时,不幸的事情发生了!
Unset之后,虽然没有变量指向该zval,但是该zval却不能被GC(指PHP5.3之前的单纯引用计数机制的GC)清理掉,$a 被释放,但是$a里的$a[1]也指向了该zval,它没有被释放,导致zval的refcount均大于0。这样,这些zval实际上会一直存在内存中,直到请求结束(参考SAPI的生命周期)。在此之前,这些zval占据的内存不能被使用,便白白浪费了,换句话说,无法释放的内存导致了内存泄露。
이런 종류의 메모리 누수가 한 번 또는 몇 번만 발생하면 나쁘지 않지만 수천 건의 메모리 누수가 발생하면 큰 문제가 됩니다. 특히 장기 실행 스크립트(예: 중단 없이 항상 백그라운드에서 실행되는 데몬)에서는 메모리를 재활용할 수 없으므로 결국 시스템에 "더 이상 사용할 수 있는 메모리가 없게" 되므로 이 작업을 피해야 합니다.
가비지 수집 메커니즘:
1.php는 원래 참조 카운터를 사용하여 메모리 재활용을 달성합니다. 즉, 여러 PHP 변수가 동일한 메모리를 참조할 수 있습니다. 이 경우 설정을 해제합니다. 그 중 하나는 메모리를 해제하지 않습니다.
예: $a
= 1; $b = $a; unset($a);//$a开辟的内存不会回收
2. 변수 범위를 벗어나면 변수가 차지하는 메모리가 해제됩니다. be 자동 정리(정적 변수를 포함하지 않으며 스크립트가 로드될 때 정적 변수가 생성되고 스크립트가 끝나면 해제됩니다),
예를 들어 함수나 메서드 내에서 지역 변수를 설정 해제하면 함수 외부에서 볼 때 메모리가 줄어들지 않습니다.
3. 참조 카운트에 결함이 있습니다. 즉, 순환 참조가 발생하면 카운터를 0으로 초기화할 수 없습니다. 메모리 사용량이 끝까지 계속됩니다. 페이지 액세스.
이 문제를 해결하기 위해 PHP5.3에는 가비지 수집 메커니즘이 추가되었습니다. 자세한 내용은 http://www.php.cn/
가비지 수집 메커니즘은 Lisp에서 처음 제안되었습니다. 수집정보
위 내용은 PHP 커널 저장 메커니즘(분리/변경)에 대한 자세한 그래픽 코드 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!