# Memóriakezelés a PHP-ban: referenciaszámlálás és szemétgyűjtés

> A PHP memóriakezelése érthetően: referenciaszámlálás, copy-on-write, ciklusok és szemétgyűjtő, mérőfüggvények, és hogy a PHP2Shell miért éppen itt támad.

Forrás: https://phpsos.de/hu/php/speicherverwaltung/  
Állapot: 2026. 10. 25.  
Szolgáltató: PHP SOS, PHP-vészhelyzeti segítség és szerverbiztonság. Azonnali segítség WhatsAppon: https://wa.me/491736494914?text=%C3%9Cdv%C3%B6zl%C3%B6m%2C%20PHP%20SOS%21%20Seg%C3%ADts%C3%A9gre%20van%20sz%C3%BCks%C3%A9g%C3%BCnk%20a%20PHP-szerver%C3%BCnkkel.%20C%C3%A9g%3A%20%E2%80%A6%20Weboldal%3A%20%E2%80%A6

**Röviden:** A PHP a memóriát elsősorban referenciaszámlálással szabadítja fel: minden érték tudja, hány változó mutat rá, és törlődik, amint ez a számláló eléri a nullát. Azokra a körkörös hivatkozásokra, amelyeket így sosem szabadítana fel, ciklusokat kezelő szemétgyűjtő (garbage collector) van. Minden kérés végén a PHP ezen felül rendbe teszi mindazt, amit a kérés lefoglalt. A PHP2Shell mindkét rése memóriaállapotokon támad: egy Out-of-Memory-állapoton a kérés feldolgozása közben, és olyan belső állapoton, amely a felszabadítás után túl sokáig megmarad.

## Hogyan kezeli a PHP a memóriát: áttekintés

Az olyan nyelvekben, mint a C, a programozónak magának kell memóriát kérnie és ismét felszabadítania. A PHP ezt átveszi. Minden változó, minden tömb és minden objektum memóriát foglal, amelyet a PHP a saját memóriakezelőjén keresztül kezel. Ennek határát a `memory_limit` szabja meg, a kézikönyv szerint alapértelmezés szerint `128M`.

A döntéshozók számára egy pont központi: a klasszikus, PHP-FPM-mel vagy mod_php-val végzett üzemben hosszú életű worker-folyamatok futnak, amelyek egymás után sok kérést dolgoznak fel. Amit egy kérés maga után hagy, azt a következő előtt megbízhatóan rendbe kell tenni. A PHP2Shell egyik rése éppen ezen a ponton van.

## Referenciaszámlálás: számoljuk, ki használ egy értéket

A PHP minden értéket egy zval nevű tárolóban tárol. A típus és a tartalom mellett a kézikönyv szerint referenciaszámlálót (`refcount`) vezet, valamint egy jelölést arról, hogy az érték része-e egy `&` jellel képzett PHP-referenciának (`is_ref`).

```php
<?php
$a = str_repeat('x', 1000);  // egy érték, refcount 1
$b = $a;                     // nincs másolás, refcount 2
$b .= 'y';                   // csak most másolódik (copy-on-write)
unset($a);                   // az eredeti refcountja csökken
// Ha egy számláló eléri a 0-t, a PHP azonnal felszabadítja a memóriát.
```

Az elv neve copy-on-write: a PHP csak akkor másol egy értéket, ha valamelyik változó módosítja. Ez sok memóriát spórol. Egy változó számlálóját az Xdebug bővítménnyel és az `xdebug_debug_zval()` függvénnyel lehet láthatóvá tenni. A kézikönyv felhívja a figyelmet, hogy a pontos számok PHP-verziónként eltérhetnek.

## Ciklusok: amikor a referenciaszámlálás nem elég

A referenciaszámlálásnak ismert gyengéje van. Ha egy szerkezet önmagára mutat, a számlálója soha nem csökken nullára.

```php
<?php
$a = ['eins'];
$a[] = &$a;   // a tömb önmagára mutató referenciát tartalmaz
unset($a);    // már senki sem férhet hozzá, de a refcount 1 marad
```

A kézikönyv szerint az ilyen körkörös hivatkozások különösen objektumoknál fordulnak elő, például szülő-gyermek szerkezetekben vagy értelmezőkben. Az egyes szivárgások ártalmatlanok, mert a PHP a kérés végén rendet tesz. A hosszan futó folyamatokban, például várakozási sor workerekben vagy nagy tesztfuttatásokban azonban több millió belőlük valódi memóriaproblémává összegződhet.

## A ciklusokat kezelő szemétgyűjtő

A PHP 5.3.0 óta a PHP Bacon és Rajan algoritmusát használja az ilyen ciklusok megtalálására. Az alapgondolat: ciklus csak akkor keletkezhet, ha egy számláló nullánál nagyobb értékre csökken. A PHP csak ezeket az értékeket jegyzi meg lehetséges gyökérként.

1. A lehetséges gyökerek egy pufferbe kerülnek. A kézikönyv alapértelmezett méretként 10 000 bejegyzést nevez meg; az éppen érvényes küszöbértéket a `gc_status()` mutatja a `threshold` mezőben.
2. Ha a puffer megtelt, a PHP végigjárja az összes elérhető értéket, és próbaképpen csökkenti a számlálóikat.
3. A második menetben az érvényes: a nulla számlálójú értékek szemétnek számítanak, az összes többinél a csökkentést visszavonják.
4. A gyökereket eltávolítják a pufferből, és a szemétként felismert összes értéket felszabadítják.

Ezt a `zend.enable_gc` (alapértelmezés szerint bekapcsolva) vagy futásidőben a `gc_enable()` és a `gc_disable()` vezérli. A `gc_collect_cycles()` futást kényszerít ki, és visszaadja az összegyűjtött ciklusok számát.

## A memória mérése és megfigyelése

| Függvény | Cél |
|---|---|
| `memory_get_usage()` | a szkript által jelenleg lefoglalt memória |
| `memory_get_usage(true)` | a memóriakezelő által a rendszertől lefoglalt memória; a `memory_limit` ezzel az értékkel szemben kerül ellenőrzésre |
| `memory_get_peak_usage()` | a lefoglalt memória csúcsértéke |
| `gc_status()` | futások, összegyűjtött ciklusok, küszöbérték, gyökerek; a PHP 8.3.0 óta futásidők is |
| `gc_collect_cycles()` | a ciklusgyűjtés azonnali kiváltása |
| `WeakMap` | objektumokhoz rendelés azok felszabadulásának megakadályozása nélkül, a PHP 8.0 óta |

A kézikönyv szerint a `memory_get_usage()` csak azt a memóriát számolja, amelyet a PHP a saját memóriakezelőjén át kér. Amit a bővítmények közvetlenül az operációs rendszertől szereznek, az hiányzik ezekből a számokból. A teljes folyamat memóriahatáráért ezért az operációs rendszer vagy a konténer felel.

## Memóriakezelés és PHP2Shell

Mindkét rés leírása közvetlenül ehhez a témához vezet. A CVE-2026-109618-nál egy Out-of-Memory-állapot megszakíthatja a PHP-t, miközben a kéradatok csak részben vannak inicializálva; a takarításkor érvénytelen adatok ilyenkor érvényesként kezelődhetnek. Hogy memóriatúlcsordulásnál mi történik belül, azt a [memory_limit és Out-of-Memory](https://phpsos.de/hu/php/out-of-memory/) oldal magyarázza. A CVE-2026-109619-nél egy kérés belső állapota a memória felszabadítása után megmaradhat, és egy későbbi kérés ugyanabban a workerben felhasználhatja, mielőtt visszaállnának. A folyamatot az indulástól a takarításig [A kérés életciklusa](https://phpsos.de/hu/php/request-lebenszyklus/) oldal írja le.

Átmeneti intézkedésként a php.net azt ajánlja, hogy a `memory_limit=-1` úgy legyen beállítva, hogy az alkalmazások ne írhassák felül, például az FPM-poolban `php_admin_value`-val, a memóriát pedig konténerekkel vagy cgroupokkal korlátozzák, nem az `RLIMIT_AS` vagy az `RLIMIT_DATA` segítségével. Hogyan működik ez a systemddel, azt a [Memóriahatárok beállítása systemd-vel és cgroupokkal](https://phpsos.de/hu/blog/speichergrenzen-systemd-cgroups/) cikk mutatja.

```ini
; FPM-pool: a php.net szerinti átmeneti intézkedés, utána minden workert újra kell indítani
php_admin_value[memory_limit] = -1
```

> **Fontos:**
> Konténeren vagy cgroupon keresztüli korlát nélkül egyetlen folyamat is lefoglalhatja a szervert. Egyetlen beállítás sem helyettesíti a frissítést a PHP 8.5.12, 8.4.27, 8.3.36 vagy 8.2.35 verzióra az összes worker újraindításával, mert csak az újraindítás távolítja el a régi folyamatokat a memóriából.

## Hogyan segít konkrétan a PHP SOS?

- Megvizsgáljuk a poolonkénti érvényes memóriabeállításokat, és azt, hogy az alkalmazások felülírhatják-e őket.
- Az átmeneti intézkedéseket konténer- vagy cgroup-szintű memóriahatárral valósítjuk meg, és teszteljük a terhelést.
- Telepítjük a frissítést, újraindítjuk az összes workert, és igazoljuk, hogy már nem fut régi folyamat, a [PHP-javítási szolgáltatásunk](https://phpsos.de/hu/leistungen/php-patch-service/) keretében.
- 2026. október 20-tól kiértékeljük a memóriahibákat és a megszakadt workereket a naplókban.


## Gyakori kérdések

**Mi az a referenciaszámlálás a PHP-ban?**  
A PHP minden értéknél számolja, hány változó vagy elem hivatkozik rá. Ha a számláló nullára csökken, a memória azonnal felszabadul. Így a memória nagy részét külön takarítási menet nélkül adja vissza.

**Mire kell akkor még a PHP-nak szemétgyűjtő?**  
A ciklusokhoz. Ha egy tömb önmagára hivatkozik, vagy két objektum ismeri egymást, a számláló soha nem csökken nullára, még ha senki nem is fér hozzá. A ciklusokat kezelő szemétgyűjtő megtalálja és eltávolítja az ilyen szerkezeteket.

**Felszabadítja a PHP a memóriát a kérés végén?**  
Igen, azt a memóriát, amelyet egy szkript a kérés alatt lefoglalt, a PHP a kérés végén rendbe teszi. A PHP2Shellnél a CVE-2026-109619 viszont éppen arra az esetre vonatkozik, hogy a belső állapot a felszabadítás után megmaradhat, és ugyanabban a workerben egy későbbi kérés felhasználhatja.

**Miért ajánlja a php.net a memory_limit=-1 értéket átmeneti intézkedésként?**  
A CVE-2026-109618 rés egy Out-of-Memory-állapottal függ össze. A php.net azt ajánlja, hogy a memory_limit=-1 úgy legyen beállítva, hogy az alkalmazások ne írhassák felül, a memóriát pedig konténerekkel vagy cgroupokkal korlátozzák. Ez nem teljes védelem, és nem helyettesíti a frissítést.

## Források

- [PHP-kézikönyv: A referenciaszámlálás alapjai](https://www.php.net/manual/de/features.gc.refcounting-basics.php)
- [PHP-kézikönyv: Ciklusok összegyűjtése](https://www.php.net/manual/de/features.gc.collecting-cycles.php)
- [PHP-kézikönyv: gc_status](https://www.php.net/manual/de/function.gc-status.php)
- [PHP-kézikönyv: memory_get_usage](https://www.php.net/manual/en/function.memory-get-usage.php)
- [php.net: Biztonsági közlemények](https://www.php.net/)
