# Správa pamäte v PHP: počítanie referencií a garbage collection

> Správa pamäte v PHP zrozumiteľne: počítanie referencií, copy-on-write, cykly a garbage collector, merace funkcie a prečo PHP2Shell útočí práve tu.

Zdroj: https://phpsos.de/sk/php/speicherverwaltung/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručne:** PHP uvoľňuje pamäť predovšetkým počítaním referencií: každá hodnota vie, koľko premenných na ňu ukazuje, a zmaže sa, len čo toto počítadlo dosiahne nulu. Pre kruhové odkazy, ktoré by sa takto nikdy neuvoľnili, existuje garbage collector cyklov. Na konci každej požiadavky PHP navyše upratuje všetko, čo požiadavka obsadila. Obe zraniteľnosti PHP2Shell útočia na stavy pamäte: na stav Out-of-Memory počas spracovania požiadavky a na interný stav, ktorý po uvoľnení zostáva príliš dlho zachovaný.

## Ako PHP spravuje pamäť: prehľad

V jazykoch ako C si musí programátor pamäť vyžiadať a znova uvoľniť sám. PHP to preberá. Každá premenná, každé pole a každý objekt obsadzuje pamäť, ktorú PHP spravuje vlastným správcom pamäte. Hranicu určuje `memory_limit`, podľa príručky štandardne `128M`.

Pre rozhodovateľov je kľúčový jeden bod: pri klasickej prevádzke s PHP-FPM alebo mod_php bežia dlhožijúce procesy workerov, ktoré postupne spracúvajú mnoho požiadaviek. Čo požiadavka zanechá, musí byť pred ďalšou spoľahlivo upratané. Práve na tomto mieste leží jedna zo zraniteľností PHP2Shell.

## Počítanie referencií: počítať, kto hodnotu používa

PHP ukladá každú hodnotu do kontajnera, takzvaného zval. Popri type a obsahu vedie podľa príručky počítadlo referencií (`refcount`) a značku, či je hodnota súčasťou referencie PHP pomocou `&` (`is_ref`).

```php
<?php
$a = str_repeat('x', 1000);  // jedna hodnota, refcount 1
$b = $a;                     // žiadne kopírovanie, refcount 2
$b .= 'y';                   // až teraz sa kopíruje (copy-on-write)
unset($a);                   // refcount originálu klesá
// Keď počítadlo dosiahne 0, PHP pamäť okamžite uvoľní.
```

Princíp sa volá copy-on-write: PHP hodnotu skopíruje, až keď ju niektorá z premenných zmení. Šetrí to veľa pamäte. Počítadlo premennej možno zviditeľniť pomocou rozšírenia Xdebug a funkcie `xdebug_debug_zval()`. Príručka upozorňuje, že presné čísla sa môžu líšiť podľa verzie PHP.

## Cykly: keď počítanie referencií nestačí

Počítanie referencií má známu slabinu. Ak štruktúra ukazuje sama na seba, jej počítadlo nikdy neklesne na nulu.

```php
<?php
$a = ['eins'];
$a[] = &$a;   // pole obsahuje referenciu na samo seba
unset($a);    // nikto k nemu už nemá prístup, ale refcount zostáva 1
```

Podľa príručky sa takéto kruhové odkazy vyskytujú najmä pri objektoch, napríklad v štruktúrach rodič-dieťa alebo v parseroch. Jednotlivé úniky sú neškodné, pretože PHP na konci požiadavky upratuje. V dlho bežiacich procesoch, ako sú workery front alebo veľké testovacie behy, sa ich však môžu nazhromaždiť milióny a vytvoriť skutočný problém s pamäťou.

## Garbage collector cyklov

Od PHP 5.3.0 používa PHP na hľadanie takýchto cyklov algoritmus podľa Bacona a Rajana. Základná myšlienka: cyklus môže vzniknúť, iba ak počítadlo klesne na hodnotu väčšiu než nula. Iba tieto hodnoty si PHP zapamätá ako možné korene.

1. Možné korene skončia v zásobníku. Príručka uvádza ako štandardnú veľkosť 10 000 záznamov; aktuálne platnú prahovú hodnotu ukazuje `gc_status()` v poli `threshold`.
2. Keď je zásobník plný, PHP prejde všetky dosiahnuteľné hodnoty a skúšobne znižuje ich počítadlá.
3. V druhom prechode platí: hodnoty s počítadlom nula sú odpad, pri všetkých ostatných sa zníženie vráti späť.
4. Korene sa odstránia zo zásobníka a všetky hodnoty rozpoznané ako odpad sa uvoľnia.

Riadi sa to cez `zend.enable_gc` (štandardne zapnuté) alebo za behu pomocou `gc_enable()` a `gc_disable()`. `gc_collect_cycles()` vynúti beh a vráti počet pozbieraných cyklov.

## Meranie a sledovanie pamäte

| Funkcia | Účel |
|---|---|
| `memory_get_usage()` | pamäť, ktorú skript práve obsadzuje |
| `memory_get_usage(true)` | pamäť, ktorú správca pamäte rezervoval u systému; voči tejto hodnote sa kontroluje `memory_limit` |
| `memory_get_peak_usage()` | maximum obsadenej pamäte |
| `gc_status()` | behy, pozbierané cykly, prahová hodnota, korene; od PHP 8.3.0 aj časy behu |
| `gc_collect_cycles()` | okamžité spustenie zbierania cyklov |
| `WeakMap` | priradenie k objektom bez toho, aby sa bránilo ich uvoľneniu, od PHP 8.0 |

Podľa príručky zachytáva `memory_get_usage()` iba pamäť, ktorú PHP vyžiada cez vlastného správcu pamäte. To, čo rozšírenia získajú priamo od operačného systému, v týchto číslach chýba. Za hranicu pamäte celého procesu preto zodpovedá operačný systém alebo kontajner.

## Správa pamäte a PHP2Shell

Opisy oboch zraniteľností vedú priamo k tejto téme. Pri CVE-2026-109618 môže stav Out-of-Memory prerušiť PHP v čase, keď sú údaje požiadavky inicializované len čiastočne; pri upratovaní sa potom môžu neplatné údaje považovať za platné. Čo sa pri pretečení pamäte deje vnútri, vysvetľuje stránka [memory_limit a Out-of-Memory](https://phpsos.de/sk/php/out-of-memory/). Pri CVE-2026-109619 môže interný stav požiadavky po uvoľnení pamäte zostať zachovaný a neskoršia požiadavka v tom istom workeri ho môže použiť skôr, než sa vynuluje. Priebeh od štartu po upratovanie opisuje stránka [Životný cyklus požiadavky](https://phpsos.de/sk/php/request-lebenszyklus/).

Ako prechodné opatrenie php.net odporúča nastaviť `memory_limit=-1` tak, aby ho aplikácie nemohli prepísať, napríklad pomocou `php_admin_value` v poole FPM, a pamäť namiesto toho obmedziť cez kontajnery alebo cgroups, nie cez `RLIMIT_AS` alebo `RLIMIT_DATA`. Ako na to so systemd, ukazuje článok [Nastavenie limitov pamäte pomocou systemd a cgroups](https://phpsos.de/sk/blog/speichergrenzen-systemd-cgroups/).

```ini
; Pool FPM: prechodné opatrenie podľa php.net, potom reštartovať všetky workery
php_admin_value[memory_limit] = -1
```

> **Dôležité:**
> Bez limitu cez kontajner alebo cgroup môže jediný proces vyťažiť server. A žiadne nastavenie nenahrádza aktualizáciu na PHP 8.5.12, 8.4.27, 8.3.36 alebo 8.2.35 vrátane reštartu všetkých workerov, pretože iba reštart odstráni staré procesy z pamäte.

## Ako PHP SOS konkrétne pomáha?

- Kontrolujeme účinné nastavenia pamäte pre každý pool a či ich aplikácie môžu prepísať.
- Prechodné opatrenia realizujeme s limitom pamäte na úrovni kontajnera alebo cgroup a testujeme záťaž.
- Nasadzujeme aktualizáciu, reštartujeme všetky workery a dokladujeme, že už nebežia staré procesy, v rámci našej [služby záplatovania PHP](https://phpsos.de/sk/leistungen/php-patch-service/).
- Chyby pamäte a prerušené workery vyhodnocujeme v logoch od 20. 10. 2026.


## Časté otázky

**Čo je počítanie referencií v PHP?**  
PHP pre každú hodnotu počíta, koľko premenných alebo prvkov na ňu odkazuje. Keď počítadlo klesne na nulu, pamäť sa okamžite uvoľní. Väčšina pamäte sa tak vráti bez vlastného upratovacieho behu.

**Načo potom PHP ešte potrebuje garbage collector?**  
Na cykly. Ak pole odkazuje samo na seba alebo si dva objekty navzájom poznajú, počítadlo nikdy neklesne na nulu, aj keď k nim už nikto nepristupuje. Garbage collector cyklov takéto štruktúry nájde a odstráni.

**Uvoľňuje PHP pamäť na konci požiadavky?**  
Áno, pamäť, ktorú skript počas požiadavky obsadil, PHP na konci požiadavky upratuje. Pri PHP2Shell sa však CVE-2026-109619 týka práve prípadu, keď môže interný stav po uvoľnení zostať zachovaný a použiť ho môže neskoršia požiadavka v tom istom workeri.

**Prečo php.net odporúča memory_limit=-1 ako prechodné opatrenie?**  
Zraniteľnosť CVE-2026-109618 súvisí so stavom Out-of-Memory. php.net odporúča nastaviť memory_limit=-1 tak, aby ho aplikácie nemohli prepísať, a pamäť namiesto toho obmedziť cez kontajnery alebo cgroups. Nie je to úplná ochrana a nenahrádza aktualizáciu.

## Zdroje

- [Príručka PHP: Základy počítania referencií](https://www.php.net/manual/de/features.gc.refcounting-basics.php)
- [Príručka PHP: Zbieranie cyklov](https://www.php.net/manual/de/features.gc.collecting-cycles.php)
- [Príručka PHP: gc_status](https://www.php.net/manual/de/function.gc-status.php)
- [Príručka PHP: memory_get_usage](https://www.php.net/manual/en/function.memory-get-usage.php)
- [php.net: Bezpečnostné oznámenia](https://www.php.net/)
