>백엔드 개발 >PHP 튜토리얼 >PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘

PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘

不言
不言원래의
2018-04-03 16:49:321267검색

이 기사에서는 PHP의 참조 카운팅 메모리 관리 메커니즘과 가비지 수집 메커니즘을 공유합니다. 도움이 필요한 친구는 이를 참조할 수 있습니다.

참조 할당

$a = 'apple';
$b = &$a;

위 코드에서는 변수 a에 문자열을 할당하고 그런 다음 a의 참조를 변수 b에 할당합니다. 당연히 이때 가리키는 메모리는 다음과 같아야 합니다.

$a -> 'apple' <- $b

a와 b는 동일한 메모리 영역(변수 컨테이너 zval)을 가리키며, 이는 var_dump($a, $b)를 통해 얻습니다. code> <code>string(5) "apple" string(5) "apple" , 이는 예상된 결과입니다. var_dump($a, $b) 得到 string(5) "apple" string(5) "apple" ,这是我们预期的结果。

unset函数 与 引用计数

unset 函数

假如我想将 'apple' 这个字符串从内存中释放掉。我是这么做的:

unset($a);

但是通过再次打印 $a $b 两变量的信息,我得到了这样的结果:Notice: Undefined variable: astring(5) "apple" 。奇怪,$a $b 指向同一个变量容器,又明明将$a释放了,为什么$b还是'apple'

其实是这样的,unset()只是将一个变量符号a(指针)销毁了,并没有释放掉那个变量容器,所以执行完操作之后,内存指向只是变成了这样:

'apple' <- $b

引用计数

引用计数 (reference count)是每个变量容器中都会存放的一条信息,它表示当前变量容器正被多少个变量符号所引用。

正如之前的例子,unset()并没有释放变量所指向的变量容器,而只是将变量符号销毁了。同时,将变量容器中的 引用计数 减1,当引用计数为0时,也就是说当变量容器不被任何变量引用时,便会触发php的垃圾回收(错误),它便会被释放(正确)。

更正上述的一个小错误: 这种单纯的引用计数方式是 php 5.2 之前的内存管理机制,称不上是垃圾回收机制,垃圾回收机制是 php 5.3 才引入的,垃圾回收机制为的是解决这种单纯的引用计数内存管理机制的缺陷(即 循环引用导致的内存泄漏,下文会进行讲解)

回到正题,我们用代码来验证一下先前的结论:

$a = 'apple';
$b = &$a;

$before = memory_get_usage();
unset($a);
$after = memory_get_usage();

var_dump($before - $after);  // 结果为int(0),变量容器的引用计数为1,没有释放
$a = 'apple';
$b = &$a;

$before = memory_get_usage();
unset($a, $b);
$after = memory_get_usage();

var_dump($before - $after);  // 结果为int(24),变量容器的引用计数为0,得到释放

直接释放

那要怎样做才能真正释放掉 'apple' 所占用的内存呢?

利用上述方法,我们可以在 unset($a) 之后再 unset($b) ,将变量容器的所有引用都销毁,引用计数减为0了,自然就被释放掉了。

当然,还有更直接的方法:

$a = null;

直接赋值 null 会将 $a 所指向的内存区域置空,并将引用计数归零,内存便被释放。

脚本执行结束后的内存

对于一般的web程序来说(fpm模式下),php的执行是单线程同步阻塞型的,当脚本执行结束之后,脚本内使用的所有内存都会被释放。那么,我们手动去释放内存到底有意义吗?

其实关于这个问题,早有解答,推荐大家看一下鸟哥 @laruence 2012年发表的一篇文章:

请手动释放你的资源(Please release resources manually)

引用计数内存管理机制的缺陷:循环引用

现在我们来讲讲之前提到的引用计数内存管理机制的缺陷。

当一个变量容器的引用计数为0时,php会进行垃圾回收。但是,你可想过,有一种情况会导致一个变量容器的引用计数永远不会被减为0,举个例子:

$a = ['one'];
$a[] = &$a;

我们看到,$a数组第二个元素就是它本身。那么,存放数组的这个变量容器的引用计数为2,一个引用是变量a,另一个引用是这个数组的第二个元素 - 索引1

PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘

那么,如果这时我们 unset($a) ,存放数组的变量容器的引用计数会减1,但还有1个引用,就是数组的元素 1

unset 함수 및 참조 계산

unset 함수

문자열 'apple'을 메모리에서 해제하고 싶다고 가정해 보겠습니다. PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘rrreee 그러나 두 변수 $a $b의 정보를 다시 인쇄하여 다음과 같은 결과를 얻었습니다. 주의: 정의되지 않은 변수 : astring(5) "apple". 이상한 점은 $a $b가 동일한 변수 컨테이너를 가리키고 $a가 명확하게 해제된 이유는 $b가 여전히 '라는 것입니다. 사과'.

실제로는 unset()이 변수 기호 a(포인터)만 파괴하고 변수 컨테이너를 해제하지 않으므로 작업을 실행한 후 메모리 포인터는 다음과 같습니다:

rrreee

참조 카운트

참조 카운트(참조 카운트)는 각 변수 컨테이너에 저장된 정보 조각으로 현재 변수에서 사용되는 변수 기호 수를 나타냅니다. 컨테이너. 이전 예제와 마찬가지로 unset()은 변수가 가리키는 변수 컨테이너를 해제하지 않고 변수 기호만 파괴합니다. 동시에 변수 컨테이너의

참조 카운트🎜를 1씩 감소시킵니다. 참조 카운트가 0인 경우, 즉 변수 컨테이너가 어떤 변수에서도 참조되지 않는 경우 PHP의 가비지 수집이 실행되고(잘못)🎜 해제됩니다(올바릅니다). 🎜🎜🎜위에 언급된 작은 오류 수정: 이 간단한 참조 계산 방법은 PHP 5.2 이전의 메모리 관리 메커니즘이며 가비지 수집 메커니즘이라고 할 수 없습니다. 가비지 수집 메커니즘은 PHP 5.3에서만 도입되었습니다. 수집 메커니즘은 이 간단한 참조 카운팅 메모리 관리 메커니즘의 단점을 해결합니다(예: 순환 참조로 인한 메모리 누수, 아래에서 설명함). 🎜🎜🎜주제로 돌아가서 이전 결론을 확인하기 위해 코드를 사용합니다. 🎜rrreeerrreee🎜 직접 해제🎜🎜 그렇다면 'apple'이 차지하는 메모리를 실제로 해제하려면 어떻게 해야 할까요? 🎜🎜위 방법을 사용하면 unset($a)를 한 다음 unset($b)하여 변수 컨테이너에 대한 모든 참조를 삭제하고 참조 카운트를 0으로 줄일 수 있습니다. , 자연스럽게 출시되었습니다. 🎜🎜물론 더 직접적인 방법도 있습니다. 🎜rrreee🎜 null을 직접 할당하면 $a가 가리키는 메모리 영역이 비워지고 참조 횟수가 0으로 재설정됩니다. . 메모리가 해제됩니다. 🎜🎜스크립트 실행 종료 후 메모리🎜🎜일반 웹 프로그램(fpm 모드)의 경우 PHP 실행은 단일 스레드 동기 차단입니다. 스크립트 실행이 끝나면 스크립트에 사용된 모든 메모리가 해제됩니다. 그렇다면 메모리를 수동으로 해제하는 것이 의미가 있습니까? 🎜🎜사실 이 질문에 대한 답은 오랫동안 존재해 왔습니다. 2012년 Brother Bird @laruence가 게시한 기사를 모두 읽어 보시기 바랍니다. 🎜
리소스를 수동으로 해제하세요(리소스를 수동으로 해제하세요) blockquote>🎜참조 카운팅 메모리 관리 메커니즘의 결함: 순환 참조🎜🎜이제 앞서 언급한 참조 카운팅 메모리 관리 메커니즘의 결함에 대해 이야기해 보겠습니다. 🎜🎜변수 컨테이너의 참조 횟수가 0에 도달하면 PHP는 가비지 수집을 수행합니다. 그런데 변수 컨테이너의 참조 횟수가 절대 0으로 줄어들지 않는 상황이 있다고 생각해보신 적 있으신가요? 예를 들면: 🎜rrreee🎜 $a array 요소는 그 자체입니다. 그런 다음 배열을 저장하는 변수 컨테이너의 참조 개수는 2이고, 하나의 참조는 <code>a 변수이고, 다른 참조는 배열의 두 번째 요소인 인덱스 1입니다. . 🎜🎜🎜PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘🎜🎜🎜그런 다음 설정을 해제하면( $a ), 배열을 저장하는 변수 컨테이너의 참조 개수는 1만큼 감소하지만 여전히 참조가 하나 있는데, 이는 배열의 1 요소입니다. 이제 참조 구조가 됩니다. 🎜🎜 🎜🎜🎜🎜🎜변수 컨테이너의 참조 횟수가 0으로 변경되지 않았기 때문에 해제할 수 없고 현재 이를 참조하는 다른 외부 변수 기호도 없어 사용자가 방법이 없습니다. 이 구조를 지우면 영원히 기억에 남을 것입니다. 🎜🎜그래서 코드에 이러한 구조와 연산이 많이 있으면 결국 메모리 손실이나 심지어 누수로 이어질 것입니다. 🎜circular reference🎜로 인해 메모리가 해제되지 않는 문제입니다. 🎜

다행히 fpm 모드에서는 요청한 스크립트의 실행이 끝나면 PHP는 이 구조를 포함하여 스크립트에 사용된 모든 메모리를 해제합니다. 하지만 데몬 프로세스 아래의 PHP 프로그램이라면 어떻게 될까요? 스울 같은. php에서 해결해야 할 긴급 문제입니다(이미 해결되었습니다. 아래 참조).

PHP 5.3.0에 도입된 동기화 알고리즘

전통적으로 PHP가 과거에 사용했던 참조 계산 메모리 메커니즘은 순환 참조의 메모리 누수를 처리할 수 없습니다. 그러나 5.3.0 PHP 사용 문서 » 참조 계산 시스템의 동시 주기 수집에 있는 동기화 알고리즘은 이 메모리 누수 문제를 해결합니다. 이 알고리즘은 PHP의 가비지 수집 메커니즘입니다.

특정 알고리즘의 구현과 프로세스는 약간 복잡합니다. 여기서는 자세히 설명하지 않겠습니다. 또한 비교적 간단한 알고리즘 프로세스를 설명하는 여러 링크를 첨부합니다. ://php.net/ manual/zh/feat... 공식문서

http://www.cnblogs.com/leoo2s...
https://blog.csdn.net/phpkern...

드디어 , 나는 여전히 Niao 형제의 기사를 인용합니다. 이 두 단락은 문제를 설명합니다.

PHP5.2 이전에는 PHP가 리소스 관리를 위해 참조 카운트를 사용했습니다. zval의 참조 카운트가 0이면 순환 참조가 있지만 해제됩니다. (주기 참조) 그러나 웹 스크립트의 특성과 목표는 실행 시간이 짧고 오랫동안 실행되지 않기 때문에 이러한 디자인은 웹 스크립트 개발에 문제가 되지 않습니다. 결국 순환 참조로 인한 리소스 누수가 발생합니다. 즉, 요청이 끝나면 리소스를 해제하는 것이 개선 조치(백업)입니다.

하지만 PHP를 사용하는 사람들이 많아지면서 일부 백그라운드 스크립트에서 PHP를 사용하는 사람들도 많아졌습니다. 스크립트는 오랜 시간 동안 실행된다는 점입니다. 순환 참조가 있으면 참조 카운트가 사용되지 않는 리소스를 제때에 해제할 수 없게 되어 스크립트가 결국 메모리 부족으로 종료됩니다. 그래서 PHP5.3 이후에 도입했습니다. GC, 즉 사용자가 해결할 수 없는 문제를 해결하기 위해 GC를 도입했습니다.

위 내용은 PHP 참조 계산 메모리 관리 메커니즘 및 가비지 수집 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.