# Διαχείριση μνήμης στην PHP: μέτρηση αναφορών και garbage collection

> Η διαχείριση μνήμης της PHP κατανοητά: μέτρηση αναφορών, copy-on-write, κύκλοι και garbage collector, συναρτήσεις μέτρησης και γιατί το PHP2Shell στοχεύει εδώ.

Πηγή: https://phpsos.de/el/php/speicherverwaltung/  
Ημερομηνία: 25/10/2026  
Πάροχος: PHP SOS, Επείγουσα βοήθεια PHP και ασφάλεια διακομιστών. Άμεση βοήθεια μέσω WhatsApp: https://wa.me/491736494914?text=%CE%93%CE%B5%CE%B9%CE%B1%20%CF%83%CE%B1%CF%82%2C%20PHP%20SOS%2C%20%CF%87%CF%81%CE%B5%CE%B9%CE%B1%CE%B6%CF%8C%CE%BC%CE%B1%CF%83%CF%84%CE%B5%20%CE%B2%CE%BF%CE%AE%CE%B8%CE%B5%CE%B9%CE%B1%20%CE%BC%CE%B5%20%CF%84%CE%BF%CE%BD%20%CE%B4%CE%B9%CE%B1%CE%BA%CE%BF%CE%BC%CE%B9%CF%83%CF%84%CE%AE%20PHP%20%CE%BC%CE%B1%CF%82.%20%CE%95%CF%84%CE%B1%CE%B9%CF%81%CE%B5%CE%AF%CE%B1%3A%20%E2%80%A6%20%CE%99%CF%83%CF%84%CF%8C%CF%84%CE%BF%CF%80%CE%BF%CF%82%3A%20%E2%80%A6

**Εν συντομία:** Η PHP ελευθερώνει μνήμη κυρίως μέσω μέτρησης αναφορών: κάθε τιμή γνωρίζει πόσες μεταβλητές δείχνουν σε αυτήν και διαγράφεται μόλις αυτός ο μετρητής φτάσει στο μηδέν. Για κυκλικές αναφορές, που με αυτόν τον τρόπο δεν θα ελευθερώνονταν ποτέ, υπάρχει ένας garbage collector κύκλων. Στο τέλος κάθε αιτήματος η PHP καθαρίζει επιπλέον ό,τι κατέλαβε το αίτημα. Και οι δύο ευπάθειες PHP2Shell στοχεύουν καταστάσεις μνήμης: μια κατάσταση out-of-memory κατά την επεξεργασία του αιτήματος και μια εσωτερική κατάσταση που παραμένει υπερβολικά πολύ μετά την απελευθέρωση.

## Πώς η PHP διαχειρίζεται τη μνήμη: η επισκόπηση

Σε γλώσσες όπως η C, ο προγραμματιστής πρέπει να ζητά και να ελευθερώνει μόνος του τη μνήμη. Η PHP το αναλαμβάνει. Κάθε μεταβλητή, κάθε πίνακας και κάθε αντικείμενο καταλαμβάνει μνήμη που η PHP διαχειρίζεται μέσω του δικού της διαχειριστή μνήμης. Το όριο γι' αυτό το ορίζει το `memory_limit`, σύμφωνα με το εγχειρίδιο από προεπιλογή `128M`.

Για όσους αποφασίζουν ένα σημείο είναι κεντρικό: στην κλασική λειτουργία με PHP-FPM ή mod_php εκτελούνται μακρόβιες διεργασίες workers, που επεξεργάζονται το ένα μετά το άλλο πολλά αιτήματα. Ό,τι αφήνει πίσω του ένα αίτημα πρέπει να έχει καθαριστεί αξιόπιστα πριν από το επόμενο. Ακριβώς εδώ βρίσκεται μία από τις ευπάθειες PHP2Shell.

## Μέτρηση αναφορών: μέτρηση του ποιος χρησιμοποιεί μια τιμή

Η PHP αποθηκεύει κάθε τιμή σε ένα δοχείο, το λεγόμενο zval. Εκτός από τον τύπο και το περιεχόμενο, φέρει σύμφωνα με το εγχειρίδιο έναν μετρητή αναφορών (`refcount`) και μια σήμανση για το αν η τιμή αποτελεί μέρος μιας αναφοράς PHP με `&` (`is_ref`).

```php
<?php
$a = str_repeat('x', 1000);  // μία τιμή, refcount 1
$b = $a;                     // καμία αντιγραφή, refcount 2
$b .= 'y';                   // μόνο τώρα γίνεται αντιγραφή (copy-on-write)
unset($a);                   // το refcount του πρωτοτύπου μειώνεται
// Όταν ένας μετρητής φτάσει στο 0, η PHP ελευθερώνει αμέσως τη μνήμη.
```

Η αρχή ονομάζεται copy-on-write: η PHP αντιγράφει μια τιμή μόνο όταν μία από τις μεταβλητές την τροποποιήσει. Αυτό εξοικονομεί πολλή μνήμη. Τον μετρητή μιας μεταβλητής μπορείτε να τον κάνετε ορατό με την επέκταση Xdebug και τη συνάρτηση `xdebug_debug_zval()`. Το εγχειρίδιο επισημαίνει ότι οι ακριβείς αριθμοί μπορεί να διαφέρουν ανά έκδοση PHP.

## Κύκλοι: όταν η μέτρηση αναφορών δεν αρκεί

Η μέτρηση αναφορών έχει μια γνωστή αδυναμία. Αν μια δομή δείχνει στον εαυτό της, ο μετρητής της δεν πέφτει ποτέ στο μηδέν.

```php
<?php
$a = ['eins'];
$a[] = &$a;   // ο πίνακας περιέχει μια αναφορά στον εαυτό του
unset($a);    // κανείς δεν έχει πλέον πρόσβαση, αλλά το refcount μένει 1
```

Σύμφωνα με το εγχειρίδιο, τέτοιες κυκλικές αναφορές εμφανίζονται ιδιαίτερα σε αντικείμενα, για παράδειγμα σε δομές γονέα-παιδιού ή σε parsers. Οι μεμονωμένες διαρροές είναι αβλαβείς, επειδή η PHP καθαρίζει στο τέλος του αιτήματος. Σε διεργασίες μακράς διάρκειας, όπως οι workers ουρών ή οι μεγάλες εκτελέσεις δοκιμών, εκατομμύρια τέτοιων μπορεί όμως να αθροιστούν σε ένα πραγματικό πρόβλημα μνήμης.

## Ο garbage collector κύκλων

Από την PHP 5.3.0 η PHP χρησιμοποιεί έναν αλγόριθμο των Bacon και Rajan για τον εντοπισμό τέτοιων κύκλων. Η βασική ιδέα: ένας κύκλος μπορεί να δημιουργηθεί μόνο όταν ένας μετρητής μειώνεται σε τιμή μεγαλύτερη του μηδενός. Μόνο αυτές τις τιμές θυμάται η PHP ως πιθανές ρίζες.

1. Οι πιθανές ρίζες καταλήγουν σε έναν ενταμιευτή. Το εγχειρίδιο αναφέρει ως προεπιλεγμένο μέγεθος 10.000 εγγραφές· την τρέχουσα ισχύουσα τιμή κατωφλίου τη δείχνει η `gc_status()` στο πεδίο `threshold`.
2. Όταν ο ενταμιευτής γεμίσει, η PHP διατρέχει όλες τις προσβάσιμες τιμές και μειώνει δοκιμαστικά τους μετρητές τους.
3. Σε δεύτερο πέρασμα ισχύει: οι τιμές με μετρητή μηδέν είναι σκουπίδια, ενώ για όλες τις άλλες η μείωση αναιρείται.
4. Οι ρίζες αφαιρούνται από τον ενταμιευτή και όλες οι τιμές που αναγνωρίστηκαν ως σκουπίδια ελευθερώνονται.

Ελέγχεται μέσω του `zend.enable_gc` (από προεπιλογή ενεργό) ή κατά την εκτέλεση με τις `gc_enable()` και `gc_disable()`. Η `gc_collect_cycles()` επιβάλλει μια εκτέλεση και επιστρέφει τον αριθμό των κύκλων που συλλέχθηκαν.

## Μέτρηση και παρακολούθηση της μνήμης

| Συνάρτηση | Σκοπός |
|---|---|
| `memory_get_usage()` | μνήμη που καταλαμβάνει αυτή τη στιγμή το σενάριο |
| `memory_get_usage(true)` | μνήμη που δεσμεύει ο διαχειριστής μνήμης από το σύστημα· με αυτή την τιμή ελέγχεται το `memory_limit` |
| `memory_get_peak_usage()` | μέγιστη τιμή της κατειλημμένης μνήμης |
| `gc_status()` | εκτελέσεις, κύκλοι που συλλέχθηκαν, κατώφλι, ρίζες· από την PHP 8.3.0 και χρόνοι εκτέλεσης |
| `gc_collect_cycles()` | άμεση εκκίνηση της συλλογής κύκλων |
| `WeakMap` | αντιστοίχιση σε αντικείμενα χωρίς να εμποδίζεται η απελευθέρωσή τους, από την PHP 8.0 |

Σύμφωνα με το εγχειρίδιο, η `memory_get_usage()` καταγράφει μόνο μνήμη που η PHP ζητά μέσω του δικού της διαχειριστή μνήμης. Ό,τι οι επεκτάσεις παίρνουν απευθείας από το λειτουργικό σύστημα δεν περιλαμβάνεται σε αυτούς τους αριθμούς. Για το όριο μνήμης ολόκληρης της διεργασίας είναι επομένως υπεύθυνο το λειτουργικό σύστημα ή το container.

## Διαχείριση μνήμης και PHP2Shell

Οι περιγραφές και των δύο ευπαθειών οδηγούν απευθείας σε αυτό το θέμα. Στην CVE-2026-109618, μια κατάσταση out-of-memory μπορεί να διακόψει την PHP ενώ τα δεδομένα του αιτήματος είναι μόνο εν μέρει αρχικοποιημένα· κατά τον καθαρισμό, άκυρα δεδομένα μπορεί τότε να αντιμετωπιστούν ως έγκυρα. Τι συμβαίνει εσωτερικά σε μια υπέρβαση μνήμης, εξηγεί η σελίδα [memory_limit και Out-of-Memory](https://phpsos.de/el/php/out-of-memory/). Στην CVE-2026-109619, η εσωτερική κατάσταση ενός αιτήματος μπορεί να παραμείνει μετά την απελευθέρωση της μνήμης και να χρησιμοποιηθεί από μεταγενέστερο αίτημα στον ίδιο worker πριν επαναφερθεί. Τη ροή από την εκκίνηση ως τον καθαρισμό περιγράφει η σελίδα [Ο κύκλος ζωής του αιτήματος](https://phpsos.de/el/php/request-lebenszyklus/).

Ως προσωρινό μέτρο το php.net συνιστά να οριστεί το `memory_limit=-1` έτσι ώστε οι εφαρμογές να μην μπορούν να το παρακάμψουν, για παράδειγμα με `php_admin_value` στο pool του FPM, και η μνήμη να περιορίζεται αντί γι' αυτό μέσω containers ή cgroups, όχι μέσω `RLIMIT_AS` ή `RLIMIT_DATA`. Πώς γίνεται αυτό με το systemd, δείχνει το άρθρο [Ορισμός ορίων μνήμης με systemd και cgroups](https://phpsos.de/el/blog/speichergrenzen-systemd-cgroups/).

```ini
; Pool FPM: προσωρινό μέτρο σύμφωνα με το php.net, στη συνέχεια επανεκκίνηση όλων των workers
php_admin_value[memory_limit] = -1
```

> **Σημαντικό:**
> Χωρίς όριο μέσω container ή cgroup, μια μεμονωμένη διεργασία μπορεί να εξαντλήσει τον διακομιστή. Και καμία ρύθμιση δεν αντικαθιστά την ενημέρωση στην PHP 8.5.12, 8.4.27, 8.3.36 ή 8.2.35 μαζί με την επανεκκίνηση όλων των workers, επειδή μόνο η επανεκκίνηση αφαιρεί τις παλιές διεργασίες από τη μνήμη.

## Πώς βοηθά συγκεκριμένα η PHP SOS;

- Ελέγχουμε τις ενεργές ρυθμίσεις μνήμης ανά pool και αν οι εφαρμογές μπορούν να τις παρακάμψουν.
- Υλοποιούμε τα προσωρινά μέτρα με όριο μνήμης σε επίπεδο container ή cgroup και δοκιμάζουμε το φορτίο.
- Εγκαθιστούμε την ενημέρωση, επανεκκινούμε όλους τους workers και αποδεικνύουμε ότι δεν εκτελούνται πλέον παλιές διεργασίες, στο πλαίσιο της [υπηρεσίας patch PHP](https://phpsos.de/el/leistungen/php-patch-service/).
- Αξιολογούμε σφάλματα μνήμης και διακοπτόμενους workers στα logs από τις 20.10.2026.


## Συχνές ερωτήσεις

**Τι είναι η μέτρηση αναφορών στην PHP;**  
Η PHP μετρά για κάθε τιμή πόσες μεταβλητές ή στοιχεία παραπέμπουν σε αυτήν. Όταν ο μετρητής πέσει στο μηδέν, η μνήμη ελευθερώνεται αμέσως. Έτσι το μεγαλύτερο μέρος της μνήμης επιστρέφεται χωρίς δική του διαδικασία καθαρισμού.

**Σε τι χρειάζεται τότε η PHP έναν garbage collector;**  
Για τους κύκλους. Αν ένας πίνακας παραπέμπει στον εαυτό του ή δύο αντικείμενα γνωρίζουν το ένα το άλλο, ο μετρητής δεν πέφτει ποτέ στο μηδέν, ακόμη κι αν κανείς δεν έχει πλέον πρόσβαση. Ο garbage collector κύκλων εντοπίζει και αφαιρεί τέτοιες δομές.

**Ελευθερώνει η PHP τη μνήμη στο τέλος ενός αιτήματος;**  
Ναι, τη μνήμη που καταλαμβάνει ένα σενάριο κατά τη διάρκεια ενός αιτήματος, η PHP την καθαρίζει στο τέλος του αιτήματος. Στο PHP2Shell, η CVE-2026-109619 αφορά όμως ακριβώς την περίπτωση όπου εσωτερική κατάσταση παραμένει μετά την απελευθέρωση και μπορεί να χρησιμοποιηθεί από μεταγενέστερο αίτημα στον ίδιο worker.

**Γιατί το php.net συνιστά το memory_limit=-1 ως προσωρινό μέτρο;**  
Η ευπάθεια CVE-2026-109618 συνδέεται με μια κατάσταση out-of-memory. Το php.net συνιστά να οριστεί το memory_limit=-1 έτσι ώστε οι εφαρμογές να μην μπορούν να το παρακάμψουν και η μνήμη να περιορίζεται αντί γι' αυτό μέσω containers ή cgroups. Αυτό δεν είναι πλήρης προστασία και δεν αντικαθιστά την ενημέρωση.

## Πηγές

- [Εγχειρίδιο PHP: Βασικά στοιχεία της μέτρησης αναφορών](https://www.php.net/manual/de/features.gc.refcounting-basics.php)
- [Εγχειρίδιο PHP: Συλλογή κύκλων](https://www.php.net/manual/de/features.gc.collecting-cycles.php)
- [Εγχειρίδιο PHP: gc_status](https://www.php.net/manual/de/function.gc-status.php)
- [Εγχειρίδιο PHP: memory_get_usage](https://www.php.net/manual/en/function.memory-get-usage.php)
- [php.net: Δελτία ασφαλείας](https://www.php.net/)
