0Pricing
PHP Academy · Lekcja

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

  1. Jak działa Zend Engine
  2. Zarządzanie pamięcią i garbage collection
  3. OPcache i kompilacja JIT
  4. Pisanie podstawowego rozszerzenia PHP w C
← Powrót do PHP Academy