메모리 누수 진단-허용 된 메모리 크기 # 바이트 소진


98

나는 끔찍한 오류 메시지, 아마도 고통스러운 노력을 통해 PHP에 메모리가 부족합니다.

123 행의 file.php에서 #### 바이트의 허용 된 메모리 크기가 소진되었습니다 (#### 바이트 할당 시도).

한계 늘리기

수행중인 작업을 알고 제한을 늘리려면 memory_limit 참조하십시오 .

ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit

조심하세요! 문제가 아닌 증상 만 해결할 수 있습니다!

누출 진단 :

오류 메시지는 메모리 누수 또는 불필요하게 축적 된 것으로 생각되는 루프가있는 줄을 가리 킵니다. memory_get_usage()각 반복이 끝날 때마다 문을 인쇄 했으며 한계에 도달 할 때까지 숫자가 천천히 증가하는 것을 볼 수 있습니다.

foreach ($users as $user) {
    $task = new Task;
    $task->run($user);
    unset($task); // Free the variable in an attempt to recover memory
    print memory_get_usage(true); // increases over time
}

이 질문의 목적을 위해 상상할 수있는 최악의 스파게티 코드가 $user또는 어딘가에 전역 범위에 숨어 있다고 가정합시다 Task.

어떤 도구, PHP 트릭 또는 부두 디버깅이 문제를 찾고 해결하는 데 도움이 될 수 있습니까?


추신-나는 최근에 이런 정확한 유형의 문제에 봉착했습니다. 불행히도 PHP에는 자식 개체 파괴 문제가 있음을 발견했습니다. 부모 개체를 설정 해제하면 해당 자식 개체가 해제되지 않습니다. 모든 자식 개체에 대한 재귀 호출을 포함하는 수정 된 설정 해제를 사용하는지 확인해야합니다. __destruct 등등. 자세한 내용은 여기에 : paul-m-jones.com/archives/262 :: 나는 다음과 같은 일을하고 있습니다 : function super_unset ($ item) {if (is_object ($ item) && method_exists ($ item, "__destruct")) {$ 항목-> __ destruct (); } unset ($ item); }
Josh

답변:


48

PHP에는 가비지 수집기가 없습니다. 참조 카운팅을 사용하여 메모리를 관리합니다. 따라서 메모리 누수의 가장 일반적인 원인은 순환 참조와 전역 변수입니다. 프레임 워크를 사용하는 경우이를 찾기 위해 검색해야 할 코드가 많이있을 것입니다. 가장 간단한 도구는 선택적으로 호출을 수행 memory_get_usage하고 코드가 누출되는 위치로 범위를 좁히는 것입니다. xdebug 를 사용하여 코드 추적을 만들 수도 있습니다 . 실행 추적show_mem_delta.


3
하지만 조심하세요 ... 생성 된 추적 파일은 엄청날 것입니다. Zend Framework 앱에서 xdebug 추적을 처음 실행했을 때 실행하는 데 시간이 오래 걸렸고 kb 또는 MB ... GB가 아닌 크기의 파일을 생성했습니다. 이것 만 알아 두세요.
rg88

1
네, 꽤 무겁습니다. .. GB의 소리는 좀 많이 들립니다-큰 스크립트가 없다면. 몇 개의 행만 처리 할 수도 있습니다 (누수를 식별하기에 충분해야 함). 또한 프로덕션 서버에 xdebug 확장을 설치하지 마십시오.
troelskn

31
5.3 이후 PHP에는 실제로 가비지 수집기가 있습니다. 반면에 메모리 프로파일 링 기능은 xdebug에서 제거되었습니다 :(
wdev

3
+1 누수 발견! 순환 참조가있는 클래스! 이러한 참조가 unset ()되면 객체는 예상대로 가비지 수집되었습니다! 감사! :)
rinogo 2013 년

@rinogo 그래서 누출에 대해 어떻게 알았습니까? 어떤 조치를 취했는지 공유 할 수 있습니까?
JohnnyQ

11

다음은 서버에서 가장 많은 메모리를 사용하는 스크립트를 식별하는 데 사용한 트릭입니다.

예를 들어 다음 코드 조각을 파일에 저장하십시오 /usr/local/lib/php/strangecode_log_memory_usage.inc.php.

<?php
function strangecode_log_memory_usage()
{
    $site = '' == getenv('SERVER_NAME') ? getenv('SCRIPT_FILENAME') : getenv('SERVER_NAME');
    $url = $_SERVER['PHP_SELF'];
    $current = memory_get_usage();
    $peak = memory_get_peak_usage();
    error_log("$site current: $current peak: $peak $url\n", 3, '/var/log/httpd/php_memory_log');
}
register_shutdown_function('strangecode_log_memory_usage');

httpd.conf에 다음을 추가하여 사용하십시오.

php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php

그런 다음 다음 위치에서 로그 파일을 분석하십시오. /var/log/httpd/php_memory_log

touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log웹 사용자가 로그 파일에 쓰기 전에 해야 할 수도 있습니다 .


8

이전 스크립트에서 PHP가 foreach 루프 후에도 범위 내에서 "as"변수를 유지한다는 것을 한 번 발견했습니다. 예를 들면

foreach($users as $user){
  $user->doSomething();
}
var_dump($user); // would output the data from the last $user 

나는 그것을 본 이후로 미래의 PHP 버전이 이것을 고쳤는지 아닌지 확실하지 않습니다. 이 경우, 당신은 할 수 unset($user)애프터 doSomething()라인 메모리에서 지워집니다. YMMV.


13
PHP는 C / Java / etc와 같은 루프 / 조건의 범위를 지정하지 않습니다. 루프 / 조건부 내부에 선언 된 모든 것은 루프 / 조건부 (설계 상 [?])를 종료 한 후에도 여전히 범위 내에 있습니다. 반면에 메서드 / 함수는 예상대로 범위가 지정됩니다. 함수 실행이 끝나면 모든 것이 해제됩니다.
Frank Farmer

나는 그것이 디자인에 의한 것이라고 가정했습니다. 이것의 한 가지 이점은 루프 후에 마지막으로 찾은 항목 (예 : 특정 기준을 충족하는 항목)으로 작업 할 수 있다는 것입니다.
joachim

할 수 unset()있지만 객체의 경우 변수가 가리키는 위치를 변경하는 것뿐입니다. 실제로 메모리에서 제거하지 않았습니다. 어쨌든 PHP는 범위를 벗어나면 메모리를 자동으로 해제하므로 더 나은 솔루션 (OP의 질문이 아닌이 답변의 관점에서)은 짧은 함수를 사용하여 루프에서 해당 변수에 매달리지 않도록하는 것입니다. 긴.
Rich Court

@patcoll 이것은 메모리 누수와 관련이 없습니다. 이것은 단순히 배열 포인터가 변경되는 것입니다. 여기를보십시오 : prismnet.com/~mcmahon/Notes/arrays_and_pointers.html 버전 3a.
피해 Smits는

7

PHP에서 메모리 누수가 발생할 수있는 몇 가지 지점이 있습니다.

  • PHP 자체
  • PHP 확장
  • 사용하는 PHP 라이브러리
  • 당신의 PHP 코드

깊은 리버스 엔지니어링이나 PHP 소스 코드 지식 없이는 처음 3 개를 찾아 수정하는 것은 매우 어렵습니다. 마지막으로 memory_get_usage로 메모리 누수 코드에 이진 검색을 사용할 수 있습니다.


91
귀하의 답변은 일반적인 것입니다
TravisO

2
PHP 7.2조차도 핵심 PHP 메모리 누수를 수정할 수 없다는 것이 부끄러운 일입니다. 장기 실행 프로세스를 실행할 수 없습니다.
Aftab Naveed

6

나는 최근에 비슷한 상황으로 모인 응용 프로그램 에서이 문제에 직면했습니다. 많은 반복을 반복하는 PHP의 cli에서 실행되는 스크립트. 내 스크립트는 여러 기본 라이브러리에 의존합니다. 특정 라이브러리가 원인이라고 생각하고 적절한 소멸 메서드를 해당 클래스에 추가하는 데 몇 시간을 허비했습니다. 다른 라이브러리로의 긴 변환 프로세스 (동일한 문제가있을 수 있음)에 직면하여 필자의 경우 문제에 대한 조잡한 해결 방법을 생각해 냈습니다.

내 상황에서는 Linux cli에서 여러 사용자 레코드를 반복하고 각각에 대해 내가 만든 여러 클래스의 새 인스턴스를 만듭니다. PHP의 exec 메서드를 사용하여 클래스의 새 인스턴스를 만들어 해당 프로세스가 "새 스레드"에서 실행되도록하기로 결정했습니다. 다음은 제가 언급 한 내용의 매우 기본적인 샘플입니다.

foreach ($ids as $id) {
   $lines=array();
   exec("php ./path/to/my/classes.php $id", $lines);
   foreach ($lines as $line) { echo $line."\n"; } //display some output
}

분명히이 접근 방식에는 한계가 있으며, 토끼 잡을 만드는 것이 쉬울 것이기 때문에 이것의 위험성을 인식해야합니다. 그러나 드물게 더 나은 해결책을 찾을 수있을 때까지 어려운 지점을 극복하는 데 도움이 될 수 있습니다. , 내 경우와 같이.


6

나는 같은 문제를 겪었고 내 해결책은 foreach를 일반 for로 바꾸는 것이 었습니다. 세부 사항에 대해서는 잘 모르겠지만 foreach가 개체에 대한 복사본 (또는 새로운 참조)을 만드는 것처럼 보입니다. 일반 for 루프를 사용하여 항목에 직접 액세스합니다.


5

나는 당신이 PHP 매뉴얼을 확인하거나 gc_enable()쓰레기를 수집하는 기능을 추가하는 것이 좋습니다 . 그것은 메모리 누수가 코드 실행에 영향을 미치지 않는다는 것입니다.

추신 : PHP에는 gc_enable()인수를 사용하지 않는 가비지 수집기 가 있습니다.


3

최근에 PHP 5.3 람다 함수가 제거 될 때 사용되는 추가 메모리를 남긴다는 사실을 발견했습니다.

for ($i = 0; $i < 1000; $i++)
{
    //$log = new Log;
    $log = function() { return new Log; };
    //unset($log);
}

이유는 잘 모르겠지만 함수가 제거 된 후에도 람다마다 250 바이트가 추가로 소요되는 것 같습니다.


2
나는 똑같이 말하려고했다. 이 문제는 5.3.10 ( # 60139 ) 에서 수정되었습니다
Kristopher Ives

@KristopherIves, 업데이트 주셔서 감사합니다! 당신 말이 맞아요, 이것은 더 이상 문제가되지 않습니다. 그래서 나는 지금 그것들을 미친 듯이 사용하는 것을 두려워해서는 안됩니다.
Xeoncross

2

함수 후에 GC 만 수행하는 PHP에 대해 말한 내용이 참이라면, 해결 방법 / 실험으로 함수 내부에 루프의 내용을 감쌀 수 있습니다.


1
@DavidKullmann 사실 내 대답이 틀렸다고 생각합니다. 결국 run()호출되는 것은 GC가 발생해야하는 마지막 함수이기도합니다.
Bart van Heukelom

2

내가 가진 하나의 큰 문제는 create_function 을 사용하는 것 입니다. 람다 함수와 마찬가지로 생성 된 임시 이름을 메모리에 남깁니다.

메모리 누수의 또 다른 원인 (Zend Framework의 경우)은 Zend_Db_Profiler입니다. Zend Framework에서 스크립트를 실행하는 경우 비활성화되어 있는지 확인하십시오. 예를 들어 내 application.ini에 다음과 같은 내용이 있습니다.

resources.db.profiler.enabled    = true
resources.db.profiler.class      = Zend_Db_Profiler_Firebug

그 전에 약 25.000 쿼리 + 처리로드를 실행하면 메모리가 128Mb (내 최대 메모리 제한)에 도달했습니다.

설정만으로 :

resources.db.profiler.enabled    = false

20Mb 미만으로 유지하기에 충분했습니다.

그리고이 스크립트는 CLI에서 실행되었지만 Zend_Application을 인스턴스화하고 부트 스트랩을 실행하고 있었으므로 "development"구성을 사용했습니다.

xDebug 프로파일 링으로 스크립트를 실행하는 데 정말 도움이되었습니다.


2

나는 그것이 명시 적으로 언급 된 것을 보지 못했지만 xdebug 는 프로파일 링 시간 과 메모리를 훌륭하게 수행 합니다 ( 2.6 현재 ). 생성 된 정보를 가져 와서 webgrind (시간 만 해당), kcachegrind , qcachegrind 등 원하는 GUI 프런트 엔드에 전달할 수 있으며 다양한 문제의 원인을 찾을 수 있도록 매우 유용한 호출 트리와 그래프를 생성합니다. .

예 (qcachegrind) : 여기에 이미지 설명 입력


1

이 대화에 조금 늦었지만 Zend Framework와 관련된 내용을 공유하겠습니다.

php 5.3.8 (phpfarm 사용)을 설치하여 php 5.2.9로 개발 된 ZF 앱으로 작업 한 후 메모리 누수 문제가 발생했습니다. 나는 내 가상 호스트 정의에서 Apache의 httpd.conf 파일에서 메모리 누수가 발생하고 있음을 발견했습니다 SetEnv APPLICATION_ENV "development". 이 줄을 주석 처리 한 후 메모리 누수가 중지되었습니다. 내 PHP 스크립트에서 인라인 해결 방법을 찾으려고합니다 (주로 기본 index.php 파일에서 수동으로 정의).


1
질문은 그가 CLI에서 실행 중이라고 말합니다. 이는 Apache가 프로세스에 전혀 관여하지 않음을 의미합니다.
막심

1
@Maxime 좋은 지적, 나는 그것을 잡지 못했습니다. 오 글쎄, 내가 여기에 남겨둔 메모에서 임의의 Google 직원이 도움이 될 것입니다.
fronzee 2012 년

이 질문에 대한 내 대답을 확인하십시오.
Andy

애플리케이션은 환경에 따라 다른 구성을 가져야합니다. "development"환경은 보통 로그인 및 다른 환경을 가지고하지 않을 수도 프로파일의 무리가 있습니다. 줄을 주석 처리하면 응용 프로그램이 기본 환경 (일반적으로 "production"또는 "prod". 메모리 누수가 여전히 존재합니다. 그것을 포함하는 코드는 그 환경에서 호출되지 않습니다.
Marco Roy

0

여기에 언급 된 내용을 보지 못했지만 도움이 될 수있는 한 가지는 xdebug 및 xdebug_debug_zval ( 'variableName')을 사용하여 refcount를 확인하는 것입니다.

방해가되는 PHP 확장의 예를 제공 할 수도 있습니다 : Zend Server의 Z-Ray. 데이터 수집이 활성화되면 가비지 수집이 꺼진 것처럼 반복 할 때마다 메모리 사용량이 늘어납니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.