Garbage Collection (GC): τι είναι, αλγόριθμοι και συλλογή σκουπιδιών στην ανάπτυξη κινητών

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-03-29 Χρόνος ανάγνωσης: 10 λεπ

Η αυτόματη διαχείριση μνήμης μέσω συλλογής σκουπιδιών είναι βασικός μηχανισμός της πλατφόρμας Android, βασισμένος στην εικονική μηχανή ART. Σύμφωνα με Google Android Documentation, 2026, ο συλλέκτης σκουπιδιών απελευθερώνει τον προγραμματιστή από τη χειροκίνητη διαχείριση μνήμης, διαγράφοντας αυτόματα αντικείμενα στα οποία δεν υπάρχουν πλέον αναφορές. Χωρίς GC, κάθε δέσμευση αντικειμένου θα απαιτούσε ρητή κλήση free ή delete, κάτι που στο οικοσύστημα Java με εκατομμύρια αντικείμενα ανά δευτερόλεπτο είναι φυσικά αδύνατο.

Βασικά σημεία

  • Garbage Collection — αυτόματος μηχανισμός απελευθέρωσης μνήμης με διαγραφή αχρησιμοποίητων αντικειμένων σε Java και Android
  • Βασικοί αλγόριθμοι — Mark-and-Sweep, Copying Collection και Generational Collection καθορίζουν την αποτελεσματικότητα της συλλογής
  • ART και Dalvik — δύο υλοποιήσεις εικονικής μηχανής Android, όπου το ART (Android Runtime) αντικατέστησε το Dalvik από το Android 5.0
  • GC Pauses — διακοπές εκτέλεσης της εφαρμογής κατά τη συλλογή — κύρια αιτία jank και προβλημάτων απόδοσης
  • Βελτιστοποίηση GC — μείωση δεσμεύσεων, χρήση δεξαμενών αντικειμένων και σωστή επιλογή τύπων Collection μειώνουν το φορτίο στον συλλέκτη

Τι είναι το Garbage Collection (GC);

Garbage Collection (GC) — είναι η αυτόματη διαδικασία εντοπισμού και απελευθέρωσης μνήμης που καταλαμβάνεται από αντικείμενα που δεν χρησιμοποιούνται πλέον από το πρόγραμμα. Στο πλαίσιο της ανάπτυξης κινητών, το GC εφαρμόζεται στην πλατφόρμα Android μέσω της εικονικής μηχανής ART, καθώς και στην τυπική Java Virtual Machine.

Σε αντίθεση με γλώσσες με χειροκίνητη διαχείριση μνήμης (C, C++), όπου ο προγραμματιστής πρέπει να καλεί ρητά free ή delete, το GC αναλαμβάνει πλήρως την παρακολούθηση του κύκλου ζωής των αντικειμένων. Ο προγραμματιστής δημιουργεί νέα αντικείμενα μέσω του τελεστή new, ενώ ο συλλέκτης καθορίζει πότε ένα αντικείμενο γίνεται μη προσβάσιμο — δηλαδή δεν έχει πλέον καμία ενεργή αναφορά.

Βασική μετρική απόδοσης GC — ο χρόνος παύσης (pause time) και η απόδοση (throughput). Η παύση είναι το διάστημα κατά το οποίο αναστέλλεται η εκτέλεση της εφαρμογής για τη διεξαγωγή συλλογής. Σε κινητό περιβάλλον, παύσεις μεγαλύτερες από 8–16 χιλιοστά του δευτερολέπτου είναι αισθητές ως χαμένα καρέ (jank).

Σύμφωνα με Google I/O 2019, το ART στο Android 10 μείωσε τις τυπικές παύσεις GC σε 2–4 ms, που είναι 70% λιγότερο σε σύγκριση με το Dalvik στο Android 4.4. Ωστόσο, η λανθασμένη διαχείριση μνήμης — συχνή δέσμευση αντικειμένων σε βρόχους, δημιουργία προσωρινών στιγμιοτύπων χωρίς λόγο — παραμένει η κύρια αιτία προβλημάτων απόδοσης.

Πώς λειτουργεί ο συλλέκτης σκουπιδιών: βασικοί αλγόριθμοι

Όλες οι υλοποιήσεις GC σε Java και Android βασίζονται σε διάφορους θεμελιώδεις αλγόριθμους, οι οποίοι συνδυάζονται για την επίτευξη ισορροπίας μεταξύ χρόνου παύσης και πληρότητας καθαρισμού. Η κατανόηση αυτών των αλγόριθμων είναι απαραίτητη για τη σύνταξη GC-friendly κώδικα.

Mark-and-Sweep

Mark-and-Sweep — ο απλούστερος αλγόριθμος, που λειτουργεί σε δύο φάσεις. Στη φάση Mark, ο συλλέκτης διασχίζει το γράφημα αντικειμένων ξεκινώντας από τις ριζικές αναφορές (root set) — τοπικές μεταβλητές, στατικά πεδία, στοίβες νημάτων. Κάθε προσβάσιμο αντικείμενο σημειώνεται με σημαία live. Στη φάση Sweep, ο συλλέκτης διασχίζει ολόκληρο το σωρό και απελευθερώνει τη μνήμη των μη σημειωμένων αντικειμένων.

Μειονέκτημα — κατακερματισμός μνήμης: μετά το Sweep, οι ελεύθερες περιοχές εναλλάσσονται με κατειλημμένες, δυσχεραίνοντας τη δέσμευση μεγάλων αντικειμένων. Σε κινητά σενάρια αυτό είναι κρίσιμο, καθώς ο σωρός είναι συνήθως μικρός (64–512 MB σε Android).

Copying Collection

Copying Collection διαιρεί το σωρό σε δύο ημίχωρους (semi-spaces). Τα ενεργά αντικείμενα αντιγράφονται από τον έναν ημίχωρο στον άλλο συμπαγώς, χωρίς κενά. Μετά την αντιγραφή, ο παλιός ημίχωρος κηρύσσεται εξ ολοκλήρου ελεύθερος. Ο αλγόριθμος εξαλείφει πλήρως τον κατακερματισμό, αλλά απαιτεί διπλάσια μνήμη.

Σε κινητά περιβάλλοντα, το Copying Collection εφαρμόζεται από γενεαλογικούς συλλέκτες για γρήγορο καθαρισμό νεαρών αντικειμένων, τα οποία στατιστικά πεθαίνουν νωρίς (υπόθεση ασθενούς γενιάς).

Generational Collection

Generational Collection διαιρεί το σωρό σε γενιές: Young Generation (νεαρά αντικείμενα) και Old Generation (παλιά, που επέζησαν πολλών συλλογών). Η συλλογή της νεαρής γενιάς (Minor GC) εκτελείται συχνά και γρήγορα, καθώς τα περισσότερα αντικείμενα πεθαίνουν νεαρά. Η συλλογή της παλιάς γενιάς (Major GC ή Full GC) γίνεται σπανιότερα αλλά διαρκεί περισσότερο.

java
// Επίδειξη generational GC: τα νεαρά αντικείμενα πεθαίνουν γρήγορα
void processItems(List<Item> items) {
    List<Result> results = new ArrayList<>();       // ζει για όλη τη μέθοδο
    for (Item item : items) {
        Result r = new Result(item.getValue());    // πεθαίνει ακαριαία
        if (r.isValid()) {
            process(r);                               // r γίνεται σκουπίδι
        }
    }
    saveResults(results);                             // results μεταφέρεται στο Old Gen
}

Σε αυτό το παράδειγμα, τα αντικείμενα Result δημιουργούνται μέσα στο βρόχο και γίνονται αμέσως σκουπίδια — είναι ιδανικοί υποψήφιοι για Young GC. Το αντικείμενο results ζει περισσότερο και μεταναστεύει στην Old Generation. Ο διαχωρισμός γενεών επιτρέπει στο Minor GC να καθαρίζει νεαρά αντικείμενα σε χιλιοστά του δευτερολέπτου, χωρίς να αγγίζει τον παλιό σωρό.

Garbage Collection στο Android: ART και Dalvik

Το Android έχει εξελιχθεί από Dalvik VM σε ART (Android Runtime), και η υλοποίηση GC είναι μία από τις βασικές διαφορές μεταξύ τους. Η κατανόηση της αρχιτεκτονικής GC στο Android βοηθά στη σύνταξη κώδικα που ελαχιστοποιεί τις παύσεις σε πραγματικές συσκευές.

ΧαρακτηριστικόDalvik (έως 4.4)ART (5.0+)
Τύπος GCMark-and-Sweep με Concurrent MarkGenerational + Concurrent
Τυπική παύση10–30 ms2–4 ms
ΣυμπαγοποίησηΌχι (μόνο αυξανόμενος κατακερματισμός)Ναι (στο παρασκήνιο, χωρίς διακοπή εφαρμογής)
AOT-μεταγλώττισηJIT (Just-In-Time)AOT + JIT (υβριδικό)

Dalvik GC

Dalvik χρησιμοποιούσε συνδυασμό Mark-and-Sweep με concurrent φάση. Το Concurrent Mark επέτρεπε στην εφαρμογή να συνεχίσει τη λειτουργία κατά τη διάσχιση του γραφήματος αντικειμένων, αλλά η φάση Sweep απαιτούσε διακοπή όλων των νημάτων (Stop-The-World). Σε συσκευές με μικρή RAM (512 MB – 1 GB), οι παύσεις έφταναν τα 30 ms, προκαλώντας αισθητές καθυστερήσεις στη διεπαφή. Επιπλέον, το Dalvik δεν συμπαγοποιούσε το σωρό, έτσι μετά από μακρά λειτουργία αυξανόταν ο κατακερματισμός και η δέσμευση μεγάλων αντικειμένων (π.χ. Bitmap) μπορούσε να προκαλέσει OutOfMemoryError παρά την επαρκή συνολική ελεύθερη μνήμη.

ART GC

ART (Android Runtime) εισήγαγε γενεαλογικό συλλέκτη με concurrent συμπαγοποίηση. Ο σωρός διαιρείται σε τρεις περιοχές: Young, Mature (ανάλογο Old Generation) και Large Object Space (για αντικείμενα μεγέθους άνω των 12 KB). Η συλλογή Young Region γίνεται παράλληλα χωρίς διακοπή νημάτων στις περισσότερες περιπτώσεις. Στο Android 10+ εμφανίστηκε η Concurrent Copying — συμπαγοποίηση που εκτελείται σε νήμα παρασκηνίου χωρίς Stop-The-World.

Χάρη στην αρχιτεκτονική ART, οι τυπικές παύσεις GC μειώθηκαν σε 2–4 ms, και σε σενάρια με επικράτηση νεαρών αντικειμένων — σε 0.5–1 ms. Αυτό επέτρεψε σε συσκευές Android να παρέχουν σταθερά 60 FPS ακόμα και με ενεργή διαχείριση μνήμης.

Τύποι συλλεκτών σκουπιδιών σε Java

Στο οικοσύστημα Java υπάρχουν διάφορες υλοποιήσεις GC, η καθεμία με το δικό της προφίλ απόδοσης. Για ανάπτυξη Android, η επιλογή περιορίζεται στο ART, αλλά η γνώση Java GC είναι χρήσιμη για τη σύνταξη διακομιστικού μέρους κινητών εφαρμογών και για ανάπτυξη σε Kotlin Multiplatform.

Serial GC

Serial GC — μονονηματικός συλλέκτης με πλήρη διακοπή εφαρμογής (Stop-The-World). Κάθε λειτουργία Mark, Sweep και Compact εκτελείται από ένα νήμα. Η απόδοση είναι χαμηλή — δεν εφαρμόζεται σε κινητούς διακομιστές. Κατάλληλο μόνο για μικρές εφαρμογές με σωρό έως 100 MB.

Parallel GC

Parallel GC (γνωστός και ως Throughput Collector) χρησιμοποιεί πολλά νήματα για όλες τις φάσεις συλλογής. Εστιάζει στη μέγιστη απόδοση (throughput) — ελαχιστοποιεί το χρόνο που αφιερώνεται στο GC σε σχέση με το χρόνο λειτουργίας της εφαρμογής. Ενεργοποιείται με τη σημαία -XX:+UseParallelGC στο JVM.

G1 GC

G1 (Garbage-First) GC — ο προεπιλεγμένος συλλέκτης σε Java 9+. Ο σωρός διαιρείται σε περιοχές 1–32 MB. Το G1 προβλέπει το χρόνο παύσης και στοχεύει να τηρήσει το καθορισμένο όριο (προεπιλογή 200 ms). Προτεραιότητα: πρώτα καθαρίζονται οι περιοχές με τον μεγαλύτερο όγκο σκουπιδιών (εξού και η ονομασία). Το G1 είναι αποτελεσματικό για διακομιστές με μεγάλο σωρό (4–64 GB) και προβλέψιμες παύσεις.

java
// Ενεργοποίηση G1 GC με στοχευόμενη παύση 100 ms
// java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar

public class MemoryMonitor {
    private static final long THRESHOLD = 512 * 1024 * 1024; // 512 MB

    public void checkHeapUsage() {
        Runtime rt = Runtime.getRuntime();
        long used = rt.totalMemory() - rt.freeMemory();
        if (used > THRESHOLD) {
            System.out.println("Heap usage exceeded threshold: " + used);
            System.out.println("Consider reducing allocations");
        }
    }
}

Η παρακολούθηση του σωρού μέσω Runtime επιτρέπει την έγκαιρη ανίχνευση διαρροών μνήμης. Εάν το used υπερβαίνει το 80% του μέγιστου σωρού σε σταθερή λειτουργία — αυτό είναι σήμα πιθανής διαρροής ή υπερβολικής κατανάλωσης μνήμης από την εφαρμογή.

Προβλήματα GC και βελτιστοποίηση μνήμης σε κινητές εφαρμογές

Ακόμα και το σύγχρονο ART GC δεν λύνει όλα τα προβλήματα — η λανθασμένη χρήση μνήμης παραμένει η κύρια αιτία jank και ANR (Application Not Responding). Ας δούμε τα βασικά σενάρια και μεθόδους βελτιστοποίησης.

GC Pauses και Jank

GC Pauses — διακοπές νημάτων εφαρμογής κατά τη διάρκεια συλλογής. Στην οθόνη αυτό εκδηλώνεται ως χαμένα καρέ, όταν ο χρόνος μεταξύ δύο καρέ υπερβαίνει τα 16.6 ms (60 FPS). Εάν το GC διαρκεί 30 ms, σχεδιάζεται μόνο ένα καρέ αντί για δύο — ο χρήστης βλέπει τραύλισμα στη διεπαφή.

Κύριες αιτίες μεγάλων παύσεων: μεγάλος αριθμός ζωντανών αντικειμένων στην Old Generation, κατακερματισμός σωρού, συχνά Full GC. Για διάγνωση χρησιμοποιούνται τα Android Studio Profiler και systrace.

Μείωση φορτίου στο GC

Βασικός κανόνας GC-friendly κώδικα — ελαχιστοποίηση του αριθμού δεσμευόμενων αντικειμένων. Κάθε νέο αντικείμενο απαιτεί όχι μόνο δέσμευση μνήμης αλλά και επακόλουθη συλλογή. Ακόμα κι αν το GC είναι γρήγορο, 1000 επιπλέον δεσμεύσεις ανά δευτερόλεπτο σημαίνουν 1000 ελέγχους για τον συλλέκτη.

  • Αποφεύγετε τη δημιουργία αντικειμένων σε βρόχους — μεταφέρετε τη δημιουργία εκτός βρόχου, χρησιμοποιείτε ξανά τοπικές μεταβλητές
  • Χρησιμοποιείτε δεξαμενές αντικειμένων — για Bitmap, byte[] και άλλες βαριές δομές χρησιμοποιήστε Object Pool ή RecyclerView.ViewHolder
  • Προτιμάτε πρωτογενείς τύπους — int αντί για Integer, float αντί για Float αποφεύγουν την αυτοπεριτύλιξη (autoboxing)
  • Χρησιμοποιείτε SparseArray — αντί για HashMap<Integer, V> στο Android SDK υπάρχει SparseArray, LongSparseArray, που λειτουργούν με πρωτογενείς τύπους
  • StringBuilder αντί για συνένωση — κάθε πρόσθεση συμβολοσειρών δημιουργεί νέο αντικείμενο String

Διαρροές μνήμης

Διαρροή μνήμης συμβαίνει όταν ένα αντικείμενο παραμένει προσβάσιμο ενώ δεν χρειάζεται πλέον. Το GC δεν μπορεί να διαγράψει ένα τέτοιο αντικείμενο και η μνήμη εξαντλείται σταδιακά. Τυπικές αιτίες: μη αποεγγεγραμμένοι ακροατές, στατικές αναφορές σε Activity, ανώνυμες κλάσεις που συλλαμβάνουν εξωτερικό πλαίσιο και μη κλειστοί Cursor/InputStream.

java
// Διαρροή μνήμης: ανώνυμη κλάση κρατάει αναφορά στο Activity
public void startTask() {
    new Thread(new Runnable() {                    // έμμεσα κρατάει this (Activity)
        @Override
        public void run() {
            // μακροχρόνια λειτουργία...
            System.out.println("Done");
        }
    }).start();
}

// Διόρθωση: στατική εμφωλευμένη κλάση + WeakReference
private static class TaskRunnable implements Runnable {
    private WeakReference<Activity> activityRef;

    TaskRunnable(Activity activity) {
        this.activityRef = new WeakReference<>(activity);
    }

    @Override
    public void run() {
        Activity act = activityRef.get();
        if (act != null) {
            // ασφαλής εργασία με Activity
        }
    }
}

Σε αυτό το παράδειγμα, το ανώνυμο Runnable συλλαμβάνει έμμεση αναφορά στο Activity. Όσο το νήμα είναι ζωντανό — το Activity δεν μπορεί να συλλεχθεί από το GC, ακόμα κι αν ο χρήστης έχει κλείσει την οθόνη. Η διόρθωση WeakReference + static class σπάει αυτή την αλυσίδα και επιτρέπει στο Activity να αποδεσμευτεί.

Συχνές ερωτήσεις

Πώς διαφέρει το GC στο Android από το GC σε Java;

Το GC στο Android (ART) είναι γενεαλογικός συλλέκτης με concurrent συμπαγοποίηση, βελτιστοποιημένος για κινητές συσκευές με περιορισμένη μνήμη. Το Java GC (G1, ZGC) είναι συλλέκτες διακομιστή με μεγάλους σωρούς και προβλέψιμες παύσεις. ART GC δεν χρησιμοποιεί σημαίες JVM — όλες οι ρυθμίσεις γίνονται αυτόματα σε επίπεδο λειτουργικού συστήματος.

Τι είναι το Stop-The-World στο GC;

Stop-The-World — η στιγμή που ο συλλέκτης διακόπτει όλα τα νήματα της εφαρμογής για να διασχίσει με ασφάλεια το γράφημα αντικειμένων ή να απελευθερώσει μνήμη. Όσο μεγαλύτερο είναι το STW, τόσο πιο αισθητό είναι το jank. Το ART μείωσε τον τυπικό χρόνο STW σε 2–4 ms χάρη στη γενεαλογική αρχιτεκτονική.

Πώς να εντοπίσετε διαρροή μνήμης στο Android;

Χρησιμοποιήστε το Android Studio Memory Profiler — δείχνει την αύξηση του σωρού, τον αριθμό δεσμεύσεων και επιτρέπει τη λήψη Heap Dump. Για βαθιά ανάλυση χρησιμοποιήστε LeakCanary — βιβλιοθήκη που εντοπίζει αυτόματα διαρροές και δείχνει την αλυσίδα αναφορών που εμποδίζουν τη συλλογή GC.

Πότε συμβαίνει Full GC και γιατί είναι επικίνδυνο;

Full GC — πλήρης συλλογή όλων των γενεών του σωρού, συμπεριλαμβανομένης της Old Generation. Σε κινητές εφαρμογές, το Full GC μπορεί να διαρκέσει 50–200 ms, προκαλώντας αισθητό jank ή ANR. Κύριες αιτίες: κατακερματισμός σωρού, διαρροές μνήμης, υπέρβαση ορίου Old Generation.

Πώς βοηθάει το Kotlin στην αποφυγή διαρροών μνήμης;

Kotlin παρέχει coroutines με δομημένη ταυτοχρονικότητα — η ακύρωση του scope ακυρώνει αυτόματα όλα τα θυγατρικά coroutines, αποτρέποντας διαρροές. Επίσης, στο Kotlin υπάρχει ο τελεστής lazy για τεμπέλικη αρχικοποίηση και τελεστές εμβέλειας που μειώνουν τον αριθμό προσωρινών αντικειμένων.

Σύνοψη

  • Garbage Collection — αυτόματη διαχείριση μνήμης μέσω διαγραφής μη προσβάσιμων αντικειμένων, θεμέλιο του Android Runtime
  • Mark-and-Sweep — βασικός αλγόριθμος με συλλογή δύο φάσεων, υποφέρει από κατακερματισμό σωρού
  • Copying Collection — εξαλείφει τον κατακερματισμό αντιγράφοντας ζωντανά αντικείμενα σε συμπαγή ημίχωρο
  • Generational GC — διαιρεί το σωρό σε γενιές (Young/Old), επιταχύνοντας τη συλλογή νεαρών βραχύβιων αντικειμένων
  • ART σε Android — γενεαλογικός συλλέκτης με concurrent συμπαγοποίηση και παύσεις 2–4 ms, που αντικατέστησε το Dalvik στο Android 5.0
  • Βελτιστοποίηση GC — μείωση δεσμεύσεων, δεξαμενές αντικειμένων, πρωτογενείς τύποι αντί περιτυλιγμάτων και SparseArray αντί HashMap μειώνουν το φορτίο στον συλλέκτη
  • Διάγνωση — Android Studio Profiler, systrace και LeakCanary είναι τα κύρια εργαλεία για τον εντοπισμό προβλημάτων μνήμης

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης