Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

PHP Memory Leaks

Pojmem memory leaks se označují stavy, kdy aplikace není schopna uvolnit alokovanou paměť. Nedávno tento problém hlouběji rozebíral Roman -Dagi- Pichlík. A před pár dny jsem zjistil, jak katastrofálně je na tom PHP.

PHP Trošku odbočím. Před každým vypuštěním nové verze Texy! ji testuji tak, že nechám převést několik tisíc připravených souborů a porovnávám získané výsledky s referenčními. Onehdy jsem během testování měl spuštěného Správce úloh, záložku Procesy. Řeknu vám, pěkně mi klesla čelist, když jsem zahlédl, že PHP si ukouslo 900MB RAM a stále chce víc. Cosi shnilého je v mé aplikaci, pomyslel jsem si, a jal se hledat problém.

Tak jsem testovací skript + knihovnu Texy! postupně redukoval a redukoval, až jsem to zredukoval na těchto pár řádků:

class MemoryLeakClass {
  var $ref;
}

$obj = new MemoryLeakClass();
$obj->ref = & $obj;
unset($obj);

Pokud si myslíte, že příkaz unset() uvolní paměť drženou objektem, jste na omylu. Neuvolní. Můžete si to ověřit tak, že spustíte tento lehce upravený příklad a budete ve Správci úloh sledovat využitou paměť:

class MemoryLeakClass {
  var $consumeBigMemory;
  var $ref;
}

$obj = new MemoryLeakClass();

// allocate 5MB
$obj->consumeBigMemory = str_repeat('*', 5e6);

$obj->ref = & $obj;
unset($obj);

// wait 10s
sleep(10);

Během čekání (sleep) je krásně vidět, že paměť zůstává blokovaná. To je mazec, co?

Příklad můžete modifikovat i tak, že vytvoříte dva objekty, které budou referencí odkazovat jeden na druhého. I když je společně zrušíte příkazem unset(), vzpomínka na ně zůstane v živé paměti.

Problém se týká prakticky všech verzí PHP, testoval jsem od 4.3.0 do 5.0.4. Prohledal jsem PHP Bugs a našel více než dva roky starý související Bug #22055 Memory leak with references in objects. Reakce jednoho z vývojářů mě dostala:

It won't be fixed in PHP 4 anyway as it has to do with the way objects

Nicméně ani v pětce to vyřešené není.

Nejvíc mě na tom celém trápí, že neznám způsob, jak ty objekty uvolnit. I když zruším $obj->ref a následně $obj, stejně to nepomůže. Takže … nevíte o nějaké učebnici Javy pro začátečníky? :-)

Komentáře

  1. David Bures http://www.websky.cz #1

    v kazdem pripade v J2ME je problem s uvolnovanim pameti pri krizove referenci taky …

    před 11 lety
  2. martinpav #2

    avatar

    Neviem ci sa jedna o rovnaky pripad, ale mam dojem ze PHP sa chova podobne aj pri spracovavani XML.
    Pri zpracovani 15MB suboru pomocou event based parseru si „odkuslo“ cca 220 MB. Moc nadseny som z toho nebol.

    před 11 lety
  3. Ľuboš Kmeťko http://www.hmw.sk/weblog/ #3

    Táto neschopnosť uvoľniť alokovanú pamať ma riadne vytrápila v jednom projekte, kde som potreboval dlhodobo bežiaci skript pripájajúci sa na rôzne web stránky a spracúvajúci ich obsah pomocou XML_HTMLSax z PEARu. Nakoniec som bol nutený to obísť periodickým volaním skriptu cez popen z iného skriptu. Skrátka kým skript neukončil beh tak sa pamäť alokovaná (aj) zrušeným objektami nijak nedala uvoľniť a skript nakoniec nakoniec prekročil limit.

    Inak na testovanie sa dá použiť aj memory_get_usage()

    před 11 lety | reagoval [4] David Grudl
  4. David Grudl http://davidgrudl.com #4

    avatar

    #3 Ľuboši Kmeťko, dá, ale ne ve windows.

    před 11 lety
  5. Petr Gurth #5

    avatar

    Na diskuzi na http://mirrors.inway.cz/…on.unset.php jsem nasel zajimave poznamky, jejichz aplikace funguje, ale…

    class MemoryLeakClass {
      var $consumeBigMemory;
      var $ref;
    
      function clearRef(){
        $this->ref = NULL;
      }
    }
    
    $obj = new MemoryLeakClass();
    
    // allocate 5MB
    $obj->consumeBigMemory = str_repeat('*', 5e6);
    
    $obj->ref = & $obj;
    //unset($obj);
    
    $obj->clearRef();
    $obj = NULL;
    
    // wait 10s
    sleep(10);
    před 11 lety | reagoval [6] David Grudl
  6. David Grudl http://davidgrudl.com #6

    avatar

    #5 Petře Gurthu, v podstatě takto jsem do Texy! přidal „destruktor“ free(), který zruší všechny proměnné v objektu:

    class Texy {
    ...
        function free() {
            foreach (array_keys(get_object_vars($this)) as $key)
                unset($this->$key);
        }
    ...
    }

    Nicméně po nějakých úpravách v kódu přestal i tento trik fungovat. Pokusím se to opět zredukovat na jednoduchý příklad a dám sem.

    Ale s tím $x = null; namísto unset($x); je to zajímavý nápad, otestuju to.

    před 11 lety
  7. Petr Gurth #7

    avatar

    Me to prave pomoci unset() nefungovalo a pomoci prirazeni NULL ano. Prijde mi to divne, nicmene v PHP se uz nedivim nicemu :)

    před 11 lety
  8. Myshpa #8

    V tomhle prikladu vse funguje jak ma (je to kombinace vyse uvedenych prikladu (null a fce free).

    Bez volani fce free() je volan destructor az po ukonceni skriptu.

    <?php
    
    class MemoryLeakClass {
    
      var $consumeBigMemory;
      var $ref;
    
      function free()
      {
        foreach (array_keys(get_object_vars($this)) as $key)
        {
            unset($this->$key);
        }
      }
    
      function __destruct()
      {
        echo "MemoryLeakClass destructed\n";
      }
    }
    
    $obj = new MemoryLeakClass();
    
    // allocate 5MB
    $obj->consumeBigMemory = str_repeat('*', 5e7);
    
    $obj->ref = & $obj;
    
    $obj->free();
    $obj = NULL;
    
    echo "sleep ...\n";
    sleep(10);
    echo "done ...\n";
    před 11 lety
  9. David Grudl http://davidgrudl.com #9

    avatar

    Ještě bych rád bych to celé trošku doplnil:

    Příkaz $var = null odstraní hodnotu, kterou odkazuje proměnná. Pokud hodnotu odkazuje více proměnných spojených referencí, bude ostraněna napříč těmito referencemi. Proměnné jako takové však budou nadále existovat.

    Příkaz unset($var) odstraní proměnnou, neovlivňuje však hodnotu.

    Pokud neexistuje už žádná proměnná odkazující na hodnotu, mělo by dojít i k uvolnění této hodnoty z paměti. Bohužel, tady PHP občas selhává. V určitých případech je třeba před odstraněním proměnné explicitně odstranit i hodnotu.

    před 11 lety
  10. Solvina #10

    avatar

    Na javu nepotrebujes ucebnici – mozna neco __velice jednoducheho, na zakladni syntaxi. Pak uz staci naucit se delat s Eclipsem, cist JavaDoc (to mi dalo zabrat nejvic) a vybrat si ze strasny spousty frameworku a zacit realizovat myslenky.
    A jo, je mi jasny, ze s tou javou si to myslel jako joke :-).
    Niceme problem s pameti popisoval i jeden z Krausu (root.cz, woko.cz a buhvico jeste) k dohledani snad na abclinuxu, rootu, nebo jinem sektarskem platku ;-) – http://www.abclinuxu.cz/…lu.cz-v-jave. To co v jave zralo 2MB zralo v php 64MB…

    před 11 lety | reagoval [11] Emo
  11. Emo http://emo-cz.net #11

    #10 Solvino,
    V uvedenem odkazu je toto: Ukázalo se, že na to, co jsem v C zpracoval zhruba ve 2 MB paměti, potřebovalo tehdy PHP neuvěřitelných 64 MB.
    Java by si IMHO taky vzala vice nez 2MB.

    před 11 lety
  12. Ondrej Ivanič #12

    Za drvivú väčšinu problémov s referenciami si môžu ludia sami.

    Stačí si len uvedomiť, že ak nepotrebujem modifikovť premenú vo funkcii tak je mi referencia zbytočná. PHP používa „copy on write“ a pokiaľ len čítam z premenej tak nenastáva žiadne spomalenie oproti predaniu cez referenciu.

    Pri objektoch sa vačšinou predaniu cez referenciu neda vyhnut, ale potom nech je referencia v signature metody/funkcie

    A druhá skupina problémov je $this mágia…

    před 11 lety
  13. char0n http://mortality.sk #13

    avatar

    Refrencie sa nedaju unsetnut funkciou unset(), ta unsetne
    len refrenciu na dane pametove miesto, a pametove miesto ostava stale naplene a uz sa s nim dalej neda pracovat. v PHP 4 je len jedine riesenie:

    function _obj_unset(&$obj)
    {
    $obj = NULL;
    unset($obj);
    }

    Najprv pametove miesto vycistime a potom unsetneme referenciu na neho.

    před 10 lety
  14. mic #14

    Nevim jestli to sem patří ale taky řešení myslím :

    Please note that PHP doesn't have magic callback to unset overloaded properties. This is the reason why unset($SomeObj->Virtual1) doesn't work.

    But it does work when we set ‚null‘ value such as the following code:

    <?php
        // now we will set 'null' value instead of using unset statement
        $SomeObj->Virtual1 = null;
        $SomeObj->{'Virtual'.(3)} = null;
    
        // and now these variables are no longer available
        print $SomeObj->Virtual1."\n";
        print $SomeObj->{'Virtual'.(3)}."\n";
    ?>
    před 10 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.