Zarządzanie pamięcią i garbage collection
Zobacz, jak działają zval, zliczanie odwołań i GC
Zarządzanie pamięcią i garbage collection to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Jak PHP zarządza pamięcią
Wartości PHP są przechowywane w zval, zarządzanych głównie za pomocą zliczania odwołań, a moduł zbierania cykli wykrywa przypadki, z którymi samo zliczanie odwołań sobie nie radzi. Zrozumienie tego mechanizmu wyjaśnia wycieki pamięci w długotrwale działających procesach, koszt kopiowania oraz sytuacje, w których znaczenie ma gc_collect_cycles().
zval
zval jest kontenerem na dowolną wartość PHP: zawiera znacznik typu oraz unię przechowującą wartość. Wartości skalarne (int, float, bool) są przechowywane przez wartość wewnątrz zval i NIE podlegają zliczaniu odwołań. Typy złożone (string, array, object) wskazują strukturę na stercie, która podlega zliczaniu odwołań.
<?php
// debug_zval_refcount-style inspection via gc + count
$arr = [1, 2, 3];
$copy = $arr; // refcount of the array bumps, no data copy yet (CoW)
// Modifying triggers copy-on-write separation
$copy[] = 4;
var_dump($arr); // still [1,2,3]
var_dump($copy); // [1,2,3,4]
?>Zliczanie odwołań
Każda wartość podlegająca zliczaniu odwołań śledzi liczbę wskazujących na nią zval. Przypisanie zwiększa licznik, a usunięcie wartości lub opuszczenie zakresu go zmniejsza. Gdy licznik osiągnie zero, pamięć jest natychmiast zwalniana — bez oczekiwania na przebieg GC. W ten sposób odzyskiwana jest większość pamięci PHP.
<?php
$a = str_repeat('x', 1000); // refcount 1 on the string
$b = $a; // refcount 2
unset($a); // refcount 1
unset($b); // refcount 0 -> freed instantly
echo "Refcounting frees deterministically\n";
?>Kopiowanie przy zapisie
Przypisanie nie kopiuje danych — współdzieli je i zwiększa licznik odwołań (copy-on-write). Rzeczywiste powielenie następuje dopiero przy pierwszym zapisie do współdzielonej wartości. Dzięki temu przekazywanie dużych tablic jest tanie, dopóki nie zostaną zmodyfikowane.
<?php
$big = range(1, 100000);
$shared = $big; // O(1): shared, refcount++
// Reads stay shared and cheap
echo count($shared), PHP_EOL;
// First write separates (copies) — now it costs O(n)
$shared[0] = -1;
echo $big[0], ' vs ', $shared[0], PHP_EOL; // 1 vs -1
?>Problem: cykle
Samo zliczanie odwołań nie może zwolnić pamięci zajmowanej przez cykle odwołań. Jeśli obiekt A przechowuje odwołanie do B, a B z powrotem do A, ich liczniki nigdy nie osiągną zera, nawet po usunięciu wszystkich zewnętrznych zmiennych — pamięć pozostaje zajęta do czasu uruchomienia modułu zbierania cykli.
<?php
class Node { public ?Node $ref = null; }
$a = new Node();
$b = new Node();
$a->ref = $b; // a -> b
$b->ref = $a; // b -> a (cycle!)
unset($a, $b); // external refs gone, but internal cycle keeps refcount > 0
// The objects are unreachable but NOT yet freed by refcounting alone.
echo gc_collect_cycles() . " cycles collected\n";
?>Moduł zbierania cykli
GC w PHP implementuje synchroniczne znakowanie i zamiatanie korzeni. Potencjalne korzenie cykli (wartości, których licznik odwołań został zmniejszony, ale nie osiągnął zera) są buforowane. Gdy bufor korzeni się zapełni (domyślnie po 10 000 elementów), uruchamiany jest GC: symuluje zmniejszanie liczników, znajduje rzeczywiście nieosiągalne grupy i je zwalnia.
<?php
// GC status exposes the root buffer and run stats
for ($i = 0; $i < 3; $i++) {
$a = new stdClass();
$b = new stdClass();
$a->b = $b; $b->a = $a; // create a cycle
unset($a, $b); // becomes a GC root
}
print_r(gc_status());
?>Sterowanie GC
Można sterować pracą modułu zbierania: gc_disable()/gc_enable() włączają go i wyłączają, a gc_collect_cycles() wymusza przebieg i zwraca liczbę zwolnionych elementów. W zadaniach wsadowych, w których kluczowa jest przepustowość, czasami wyłącza się GC na czas intensywnego etapu, a następnie uruchamia zbieranie raz na jego końcu.
<?php
gc_disable(); // no automatic cycle collection
// ... heavy phase that creates and drops many cycles ...
for ($i = 0; $i < 1000; $i++) {
$x = new stdClass(); $y = new stdClass();
$x->y = $y; $y->x = $x; unset($x, $y);
}
$freed = gc_collect_cycles(); // reclaim all at once
gc_enable();
echo "Freed $freed objects\n";
?>Długotrwale działające procesy robocze
W demonach CLI, konsumentach kolejek oraz procesach roboczych Swoole/RoadRunner pamięć nie jest odzyskiwana podczas kończenia żądania — proces działa przez wiele godzin. Należy obserwować: pamięci podręczne statyczne, które rosną bez ograniczeń, gromadzące się cykle oraz niezamknięte zasoby. Zużycie można monitorować za pomocą memory_get_usage(true), a procesy robocze należy okresowo restartować.
<?php
$peakStart = memory_get_usage(true);
// Simulate processing a batch of jobs
for ($job = 0; $job < 5; $job++) {
$payload = range(0, 10000);
// ... process ...
unset($payload);
}
printf("start: %d KB, peak: %d KB, now: %d KB\n",
$peakStart >> 10,
memory_get_peak_usage(true) >> 10,
memory_get_usage(true) >> 10
);
?>WeakReference i WeakMap
Aby buforować dane powiązane z obiektem bez utrzymywania go przy życiu, należy użyć WeakReference (8.0) i WeakMap (8.0). Wpis w WeakMap nie zwiększa licznika odwołań obiektu będącego kluczem, dlatego obiekt może zostać zebrany, a jego wpis automatycznie usunięty — to idealne rozwiązanie dla pomocniczych tablic metadanych, które nie mogą powodować wycieków.
<?php
$map = new WeakMap();
$obj = new stdClass();
$map[$obj] = 'metadata'; // does NOT keep $obj alive
echo count($map) . PHP_EOL; // 1
unset($obj); // object collectible -> entry removed
echo count($map) . PHP_EOL; // 0
?>Internowane ciągi znaków i wartości niezmienne
Ciągi literałowe w kodzie źródłowym są internowane: przechowywane tylko raz, współdzielone i bez zliczania odwołań ani zwalniania podczas obsługi żądania. OPcache przenosi internowane ciągi oraz niezmienne (buforowane) op_arrays do pamięci współdzielonej między żądaniami. Dlatego kopiowanie literałów tekstowych jest praktycznie bezkosztowe.
Limit pamięci i alokator
PHP korzysta z własnego alokatora emalloc (Zend Memory Manager), zbudowanego nad alokatorem systemowym, i śledzi zużycie pamięci w ramach żądania względem ustawienia memory_limit. Osiągnięcie limitu powoduje błąd krytyczny. Wartość true w memory_get_usage(true) raportuje rzeczywiście przydzielone przez system operacyjny bloki, a nie tylko pamięć aktualnie zajmowaną przez skrypt.
<?php
echo 'limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'emalloc usage: ' . memory_get_usage() . " bytes\n"; // ZendMM tracked
echo 'real usage: ' . memory_get_usage(true) . " bytes\n"; // from OS
?>Szybki test
Dlaczego samo zliczanie odwołań może powodować wycieki pamięci niektórych obiektów?
Podsumowanie
PHP przechowuje wartości w zval; wartości skalarne są przechowywane przez wartość, a typy złożone są alokowane na stercie i podlegają zliczaniu odwołań z kopiowaniem przy zapisie. Zliczanie odwołań zwalnia pamięć deterministycznie po osiągnięciu zera, ale nie może odzyskać pamięci zajmowanej przez cykle, dlatego synchroniczny moduł znakowania i zamiatania uruchamia się po zapełnieniu bufora korzeni (lub za pośrednictwem gc_collect_cycles()). Do nieposiadających obiektów powiązań należy używać WeakMap/WeakReference, a w długotrwale działających procesach roboczych obserwować nieograniczony wzrost pamięci.
Często zadawane pytania
Czy lekcja „Zarządzanie pamięcią i garbage collection” jest bezpłatna?
Tak — pełny tekst „Zarządzanie pamięcią i garbage collection” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Zarządzanie pamięcią i garbage collection”?
Zobacz, jak działają zval, zliczanie odwołań i GC Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Zarządzanie pamięcią i garbage collection”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Jak działa Zend Engine
- Zarządzanie pamięcią i garbage collection
- OPcache i kompilacja JIT
- Pisanie podstawowego rozszerzenia PHP w C