Member-only story
🕵️ The Memory Leak That Wasn’t: Doctrine, Cycles and PHP’s Garbage Collector
Every number in this article comes from a real run: PHP 8.3.6, Doctrine ORM 3.7.6, DBAL 4.5.0 and SQLite.
🕵️ The import that would not behave
You wrote an import for 100,000 orders. You flush every 500 rows and call $em->clear(). The Doctrine manual tells you to. Then a colleague adds gc_collect_cycles() after every batch. Another colleague swears that gc_disable() makes the import faster. Every team has this argument once a year, usually right after a container dies at 2 a.m. Who is right?
Neither of them can win by opinion. So we will measure. We instrument the import with memory_get_usage(true) and gc_status(), prove in a few lines that Doctrine entities form cycles that survive clear(), and compare three collector settings on the same data.
Not a Medium member yet? Click here to access this article for free!
🔢 How PHP frees memory
PHP counts references. Every value carries a counter. Assign the value to a variable and the counter goes up. Unset the variable and it goes down. At zero, PHP frees the value on the spot. This costs almost nothing and never pauses your script.








