# memory_limit ir Out-of-Memory: kas vyksta viduje

> PHP Out of Memory suprantamai: kaip memory_limit skaičiuoja, kas viduje vyksta nutraukimo metu ir ką tai reiškia CVE-2026-109618 bei jūsų serveriams.

Šaltinis: https://phpsos.de/lt/php/out-of-memory/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** PHP Out-of-Memory klaida atsiranda, kai skriptas bando užsiprašyti daugiau atminties, nei leidžia nustatymas `memory_limit` (numatytoji reikšmė: 128 MB vienai užklausai). Tuomet PHP nutraukia užklausą su mirtina klaida ir sutvarko. Būtent šis nutraukimo ir sutvarkymo kelias buvo klaidingas esant CVE-2026-109618, vienai iš dviejų PHP2Shell spragų. Nuo PHP 8.5.12, 8.4.27, 8.3.36 ir 8.2.35 `memory_limit` yra minkštoji riba, papildyta kietąja riba `memory_hard_limit_percent`.

## Ką reiškia „Allowed memory size exhausted“?

Gerai žinomas klaidos pranešimas maždaug toks: „Allowed memory size of 134217728 bytes exhausted (tried to allocate …)“. Pirmasis skaičius yra nustatyta riba baitais, čia 128 MB. Taigi skriptas bandė užimti atmintį virš šios ribos, pavyzdžiui, įkeldamas didelį failą, atlikdamas milžinišką duomenų bazės užklausą ar dėl begalinio ciklo.

Sprendimų priėmėjams svarbu: riba galioja kiekvienam PHP procesui ir kiekvienai užklausai, o ne visam serveriui. Ji saugo serverį nuo to, kad vienas klaidingas skriptas sunaudotų visą atmintį. Bendra apkrova susidaro tik iš kartu veikiančių darbinių procesų (worker) skaičiaus. Esant 20 PHP-FPM darbinių procesų su 256 MB riba, skaičiuojant tai yra daugiau nei 5 GB.

## Kaip PHP skaičiuoja atmintį

PHP atmintį užklausoms valdo per savo atminties tvarkytuvą, Zend Memory Manager. Jis paima didesnius blokus iš operacinės sistemos ir viduje paskirsto juos kintamiesiems, masyvams ir objektams. Pagal šią rezervuotą reikšmę PHP tikrina `memory_limit`. Atmintis, kurią plėtinys užsiprašo tiesiai iš operacinės sistemos, pasak vadovo, šiame skaičiavime nefigūruoja.

Reikšmes galite pamatyti kode:

```php
<?php
echo memory_get_usage(), PHP_EOL;       // šiuo metu skripto užimta atmintis
echo memory_get_usage(true), PHP_EOL;   // atminties tvarkytuvo rezervuota; pagal ją tikrina memory_limit
$liste = range(1, 200000);
echo memory_get_peak_usage(), PHP_EOL;  // iki šiol didžiausia reikšmė
unset($liste);
memory_reset_peak_usage();              // atstatyti didžiausią reikšmę, nuo PHP 8.2
```

Kaip nuorodų skaičiavimas ir šiukšlių surinkimas atmintį vėl atlaisvina, paaiškina puslapis [Atminties valdymas PHP](https://phpsos.de/lt/php/speicherverwaltung/).

## Kas viduje vyksta atminties trūkumo metu

Supaprastintai atminties nutraukimas vyksta tokiais žingsniais:

1. Skriptas užsiprašo atminties, pavyzdžiui, naujam masyvo elementui arba skaitydamas užklausos duomenis.
2. Atminties tvarkytuvas patikrina, ar užklausa viršija ribą.
3. Jei taip, PHP sukelia mirtiną klaidą ir iškart palieka vykdomą apdorojimą, net pusiaukelėje pusiau atliktos užduoties.
4. Užregistruotos užbaigimo funkcijos (shutdown) dar vykdomos; per jas daug programų klaidą užprotokoluoja.
5. Pabaigoje PHP sutvarko užklausą ir atlaisvina jos atmintį. Darbinis procesas po to laukia kitos užklausos.

Jautrus taškas yra 5 žingsnis. Sutvarkymas turi gebėti tvarkytis su duomenų struktūromis, kurios nutraukimo metu buvo sukurtos tik iš dalies. Pasak PHP projekto saugumo politikos, įprastas atminties nutraukimas nelaikomas saugumo spraga, kol riba atmeta užklausą ir baigiasi tik dabartinė užklausa. Esant CVE-2026-109618, būtent ši prielaida buvo pažeista: pasak php.net, atminties trūkumo būsena galėjo pertraukti PHP, kai vidiniai užklausos duomenys buvo inicializuoti tik iš dalies, o sutvarkymas neteisingus duomenis traktavo kaip teisingus. Išnaudojimo detalių neskelbiame; spragos įvertinimas pateiktas puslapyje [CVE-2026-109618](https://phpsos.de/lt/php2shell/cve-2026-109618/).

## Nauja minkštoji ir kietoji atminties riba

Su saugumo leidimais 2026-10-21 PHP projektas pakeitė elgseną. Pasak vadovo, `memory_limit` nuo tada yra minkštoji riba: užklausai, kuri ją viršija, daugeliu atvejų dar leidžiama užsibaigti, ir tik tada seka mirtina klaida. Atskira kietoji riba nustato, kiek toli tai gali nueiti.

| Direktyva | Numatytoji | Keičiama | Reikšmė |
|---|---|---|---|
| `memory_limit` | 128M | INI_ALL | Atminties riba užklausai, `-1` = be ribos |
| `memory_hard_limit_percent` | nenustatyta | INI_ALL | Kietoji riba kaip memory_limit procentas, nauja nuo 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| `post_max_size` | 8M | INI_PERDIR | Didžiausias POST duomenų dydis |
| `upload_max_filesize` | 2M | INI_PERDIR | Didžiausias vieno įkelto failo dydis |

Vadovo pavyzdys: esant `memory_limit=128M` ir `memory_hard_limit_percent=150` kietoji riba yra 192 MB. Vadovas šią direktyvą kartu nurodo kaip atpažinimo požymį: jei PHP versija jos nepažįsta, ją veikia CVE-2026-109618 ir CVE-2026-109619. Greitas bandymas kiekvienai įdiegtai PHP versijai:

```bash
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# bool(false) reiškia: direktyva nežinoma
```

Distribucijų paketams su backport paketo pakeitimų žurnalas išlieka lemiamu įrodymu, kaip aprašyta puslapyje [Kaip teisingai patikrinti PHP versiją ir pataisų būseną](https://phpsos.de/lt/php2shell/php-version-pruefen/). Taip pat atskirai patikrinkite PHP-FPM binarinį failą, nes jis gali būti kitos versijos nei komandinė eilutė.

## Atminties ribos ir PHP2Shell: į ką dabar atkreipti dėmesį administratoriams

Beveik visos php.net pereinamosios priemonės dėl CVE-2026-109618 sukasi apie atmintį ir užklausų dydžius. Jos sumažina riziką, bet neatstoja atnaujinimo:

```ini
; FPM baseinas, pvz., /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M        ; 32M, jei reikalingi įkėlimai
php_admin_value[upload_max_filesize] = 6M  ; laikyti žemiau post_max_size
```

Čia lemia trys dalykai. Pirma, jokia programa, joks katalogas ir joks kitas baseinas neturi vėl nustatyti baigtinės ribos; php.net aiškiai reikalauja šios kontrolės. Antra, paslaugai su `memory_limit=-1` reikia išorinės ribos per konteinerį ar cgroup, pasak php.net, geriausia taip, kad būtų nutraukiamas tik paveiktas darbinis procesas (worker). `RLIMIT_AS` ir `RLIMIT_DATA` pasak php.net netinka. Trečia, php.net rekomenduoja ribas žiniatinklio serveryje ar tarpiniame serveryje (proxy) užklausos dydžiui, laukų skaičiui, multipart dalims, antraštėms ir įdėjimo gyliui.

> **Svarbu:**
> `memory_limit=-1` be išorinės atminties ribos esant didelei apkrovai gali visiškai paralyžiuoti serverį. Pereinamąsias priemones nustatykite tik kaip paketą ir tik iki atnaujinimo.

Kaip ribą nustatyti su systemd, aprašyta straipsnyje [Atminties ribos su systemd ir cgroups](https://phpsos.de/lt/blog/speichergrenzen-systemd-cgroups/); ilgalaikį kasdienį sprendimą aprašo [kaip teisingai nustatyti memory_limit](https://phpsos.de/lt/blog/memory-limit-richtig-setzen/).

## Kaip PHP SOS padeda konkrečiai?

Per [PHP pataisų paslaugą](https://phpsos.de/lt/leistungen/php-patch-service/) veikiame taip:

- **Patikrinti esamą būklę:** užfiksuojame kiekvieną PHP versiją serveryje, atskirai pagal CLI, FPM ir Apache modulį, ir tikriname, ar veikia ištaisyta versija.
- **Sutvarkyti atminties ribas:** tikriname, kur visur nustatomas `memory_limit`, ir baseino bei sistemos ribas sutvarkome taip, kad jos derėtų.
- **Apsaugoti pereinamąjį laikotarpį:** kur atnaujinimas dar užblokuotas, php.net priemones nustatome pilnai ir apsaugotas nuo perrašymo.
- **Pateikti įrodymą:** raštu gaunate versijas, paketų būseną ir konfigūraciją prieš ir po įsikišimo.


## Dažni klausimai

**Ką reiškia pranešimas „Allowed memory size exhausted“?**  
Skriptas bandė užsiprašyti daugiau atminties, nei leidžia memory_limit. Tuomet PHP nutraukia vykdomą užklausą su mirtina klaida. Kitos užklausos ir pats serveris veikia toliau.

**Ar memory_limit galioja visam serveriui?**  
Ne, riba galioja kiekvienam PHP procesui ir kiekvienai užklausai. Jei veikia 20 FPM darbinių procesų (worker) su 256 MB riba, teoriškai kartu jie gali užimti daugiau nei 5 GB. Bendrą ribą todėl reikia nustatyti per baseino dydį, systemd ar konteinerį.

**Kas yra memory_hard_limit_percent?**  
Nauja direktyva, atsiradusi su PHP 8.5.12, 8.4.27, 8.3.36 ir 8.2.35. Ji nustato kietąją ribą kaip memory_limit procentą, kuri niekada neviršijama. Pats memory_limit nuo tada yra minkštoji riba.

**Ar memory_limit=-1 saugo nuo PHP2Shell?**  
Tik iš dalies. php.net šį nustatymą nurodo kaip pereinamąją priemonę nuo CVE-2026-109618, aiškiai pažymėdama, kad visiškos apsaugos ji neteikia. Jai būtinai reikia išorinės atminties ribos, o atnaujinimas lieka privalomas.

## Šaltiniai

- [PHP vadovas: pagrindiniai nustatymai (memory_limit, memory_hard_limit_percent)](https://www.php.net/manual/en/ini.core.php)
- [PHP vadovas: memory_get_usage()](https://www.php.net/manual/en/function.memory-get-usage.php)
- [php.net: 2026-10-21 saugumo pranešimai](https://www.php.net/)
