Napotkałem przerażający komunikat o błędzie, prawdopodobnie poprzez żmudny wysiłek, PHP zabrakło pamięci:
Wyczerpano dozwolony rozmiar pamięci #### bajtów (próbowano przydzielić #### bajtów) w pliku.php w linii 123
Zwiększenie limitu
Jeśli wiesz, co robisz i chcesz zwiększyć limit, zobacz memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Strzec się! Możesz tylko rozwiązać objaw, a nie problem!
Diagnozowanie wycieku:
Komunikat o błędzie wskazuje na linię z pętlą, która moim zdaniem przecieka lub niepotrzebnie gromadzi pamięć. Wydrukowałem memory_get_usage()
instrukcje na końcu każdej iteracji i widzę, że liczba powoli rośnie, aż osiągnie limit:
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
}
Dla celów niniejszego pytanie załóżmy najgorszy wyobrażalny kod spaghetti ukrywa się w globalnej zakres gdzieś w $user
lubTask
.
Jakie narzędzia, sztuczki PHP lub debugowanie voodoo mogą pomóc mi znaleźć i rozwiązać problem?
źródło
Odpowiedzi:
PHP nie ma garbage collectora. Wykorzystuje liczenie referencji do zarządzania pamięcią. Dlatego najczęstszym źródłem wycieków pamięci są cykliczne odwołania i zmienne globalne. Obawiam się, że jeśli używasz frameworka, będziesz musiał przeszukać dużo kodu, aby go znaleźć. Najprostszym narzędziem jest wybiórcze umieszczanie wywołań
memory_get_usage
i zawężanie ich do miejsca wycieku kodu. Możesz również użyć xdebug, aby utworzyć ślad kodu. Uruchom kod ze śladami wykonywania ishow_mem_delta
.źródło
Oto sztuczka, której użyliśmy do zidentyfikowania, które skrypty używają najwięcej pamięci na naszym serwerze.
Zapisz następujący fragment kodu w pliku, np .
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Zastosuj go, dodając do httpd.conf:
Następnie przeanalizuj plik dziennika pod adresem
/var/log/httpd/php_memory_log
Może być konieczne, aby
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
użytkownik sieci Web mógł zapisywać w pliku dziennika.źródło
Zauważyłem kiedyś w starym skrypcie, że PHP zachowywałoby zmienną „as” w zakresie nawet po wykonaniu mojej pętli foreach. Na przykład,
Nie jestem pewien, czy przyszłe wersje PHP naprawiły to, czy nie, odkąd to widziałem. Jeśli tak jest, możesz
unset($user)
podoSomething()
linii wyczyścić ją z pamięci. YMMV.źródło
unset()
to, ale pamiętaj, że w przypadku obiektów wszystko, co robisz, to zmienianie miejsca, na które wskazuje zmienna - tak naprawdę nie usunąłeś jej z pamięci. PHP i tak automatycznie zwolni pamięć, gdy znajdzie się poza zasięgiem, więc lepszym rozwiązaniem (pod względem tej odpowiedzi, a nie pytania OP) jest użycie krótkich funkcji, aby nie przywiązywali się do tej zmiennej z pętli długie.Istnieje kilka możliwych punktów wycieku pamięci w PHP:
Trudno jest znaleźć i naprawić pierwsze 3 bez głębokiej inżynierii wstecznej lub znajomości kodu źródłowego PHP. W ostatnim przypadku możesz użyć wyszukiwania binarnego kodu przeciekającego do pamięci za pomocą funkcji memory_get_usage
źródło
Niedawno napotkałem ten problem na aplikacji, w okolicznościach, które, jak sądzę, są podobne. Skrypt działający w cli PHP, który wykonuje pętle w wielu iteracjach. Mój skrypt zależy od kilku podstawowych bibliotek. Podejrzewam, że przyczyną jest konkretna biblioteka i bezskutecznie spędziłem kilka godzin próbując dodać do jej klas odpowiednie metody niszczenia. W obliczu długiego procesu konwersji do innej biblioteki (która może mieć te same problemy), wymyśliłem prymitywne obejście problemu w moim przypadku.
W mojej sytuacji na linux cli przeglądałem kilka rekordów użytkowników i dla każdego z nich tworzyłem nową instancję kilku klas, które utworzyłem. Postanowiłem spróbować stworzyć nowe instancje klas przy użyciu metody exec PHP, aby proces ten działał w „nowym wątku”. Oto naprawdę podstawowa próbka tego, do czego się odnoszę:
Oczywiście to podejście ma ograniczenia i należy zdawać sobie sprawę z niebezpieczeństw związanych z tym, ponieważ stworzenie pracy dla królika byłoby łatwe, jednak w niektórych rzadkich przypadkach może to pomóc w pokonaniu trudnego miejsca, dopóki nie zostanie znalezione lepsze rozwiązanie , jak w moim przypadku.
źródło
Natknąłem się na ten sam problem i moim rozwiązaniem było zastąpienie wszystkich zwykłych for. Nie jestem pewien szczegółów, ale wygląda na to, że każdy tworzy kopię (lub w jakiś sposób nowe odniesienie) do obiektu. Korzystając ze zwykłej pętli for, uzyskujesz bezpośredni dostęp do elementu.
źródło
Proponuję sprawdzić podręcznik php lub dodać plik
gc_enable()
funkcję zbierania śmieci ... To znaczy, że wycieki pamięci nie wpływają na działanie twojego kodu.PS: php ma garbage collector,
gc_enable()
który nie przyjmuje żadnych argumentów.źródło
Niedawno zauważyłem, że funkcje lambda w PHP 5.3 pozostawiają dodatkową pamięć używaną po ich usunięciu.
Nie jestem pewien dlaczego, ale wydaje się, że każda lambda zajmuje dodatkowe 250 bajtów, nawet po usunięciu funkcji.
źródło
Jeśli to, co mówisz o PHP wykonującym GC tylko po tym, jak funkcja jest prawdą, jest prawdą, możesz zawinąć zawartość pętli wewnątrz funkcji jako obejście / eksperyment.
źródło
run()
co się nazywa, jest także funkcją, na końcu której powinien nastąpić GC.Jednym z ogromnych problemów, które miałem, było użycie funkcji create_function . Podobnie jak w funkcjach lambda, pozostawia wygenerowaną tymczasową nazwę w pamięci.
Inną przyczyną wycieków pamięci (w przypadku Zend Framework) jest Zend_Db_Profiler. Upewnij się, że jest wyłączone, jeśli uruchamiasz skrypty w Zend Framework. Na przykład w moim pliku application.ini miałem:
Uruchomienie około 25 000 zapytań + mnóstwo przetwarzania wcześniej spowodowało, że pamięć osiągnęła niezłe 128 MB (mój maksymalny limit pamięci).
Wystarczy ustawić:
wystarczyło, żeby nie przekraczał 20 Mb
I ten skrypt działał w CLI, ale tworzył instancję Zend_Application i uruchamiał Bootstrap, więc używał konfiguracji "programistycznej".
To naprawdę pomogło w uruchomieniu skryptu z profilowaniem xDebug
źródło
Nie widziałem tego wyraźnie, ale xdebug świetnie radzi sobie z profilowaniem czasu i pamięci (od 2.6 ). Możesz pobrać informacje, które generuje i przekazać je do wybranego interfejsu GUI: webgrind (tylko czas), kcachegrind , qcachegrind lub innych, a generuje bardzo przydatne drzewa wywołań i wykresy, które pozwolą Ci znaleźć źródła różnych nieszczęść .
Przykład (z qcachegrind):
źródło
Trochę się spóźniłem na tę rozmowę, ale opowiem o czymś związanym z Zend Framework.
Miałem problem z wyciekiem pamięci po zainstalowaniu php 5.3.8 (używając phpfarm) do pracy z aplikacją ZF, która została stworzona z php 5.2.9. Odkryłem, że wyciek pamięci był wyzwalany w pliku httpd.conf Apache, w mojej definicji wirtualnego hosta, gdzie jest napisane
SetEnv APPLICATION_ENV "development"
. Po skomentowaniu tego wiersza wycieki pamięci ustały. Próbuję wymyślić wbudowane obejście w moim skrypcie php (głównie poprzez zdefiniowanie go ręcznie w głównym pliku index.php).źródło
"development"
Środowisko ma zwykle kilka zalogowaniu i profilowania, że inne środowiska mogą nie mieć. Skomentowanie wiersza spowodowało, że aplikacja użyła zamiast tego domyślnego środowiska, którym jest zwykle"production"
lub"prod"
. Wyciek pamięci nadal istnieje; kod, który go zawiera, po prostu nie jest wywoływany w tym środowisku.Nie widziałem tego tutaj, ale jedną rzeczą, która może być pomocna, jest użycie xdebug i xdebug_debug_zval ('nazwa_zmiennej'), aby zobaczyć refcount.
Mogę również podać przykład rozszerzenia php, które przeszkadza: Z-Ray serwera Zend. Jeśli zbieranie danych jest włączone, użycie pamięci będzie się powiększać przy każdej iteracji, tak jakby wyrzucanie elementów bezużytecznych było wyłączone.
źródło