Τρώει μνήμη και φουσκώνει — τι είναι, αιτίες και πώς να το αποφύγετε

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

Διαρροή μνήμης — ένα από τα πιο ύπουλα προβλήματα στην ανάπτυξη κινητών εφαρμογών. Η μνήμη της εφαρμογής αυξάνεται ασταμάτητα έως ότου φτάσει το όριο που έχει ορίσει το λειτουργικό σύστημα, οπότε ακολουθεί OutOfMemoryError ή αναγκαστικός τερματισμός. Σύμφωνα με τη Square Engineering, περίπου το 40% των Android εφαρμογών έχει τουλάχιστον μία διαρροή μνήμης που μπορεί να ανιχνευθεί μόνο με προφίλ. Ας αναλύσουμε τις αιτίες και τις μεθόδους πρόληψης της αύξησης μνήμης.

Κύρια σημεία

  • GC reachability — ένα αντικείμενο δεν διαγράφεται εάν υπάρχει ενεργή αναφορά από το root set
  • Στατικές αναφορές σε Activity ή Context — η πιο συνηθισμένη αιτία διαρροής στο Android
  • LeakCanary — το τυπικό εργαλείο για αυτόματη ανίχνευση διαρροών στο Android
  • WeakReference — λύση για αναφορές που δεν πρέπει να εμποδίζουν τη συλλογή σκουπιδιών
  • Lifecycle-aware components ακυρώνουν αυτόματα τις συνδρομές κατά την καταστροφή του view

Τι είναι η διαρροή μνήμης και το φούσκωμα εφαρμογής;

Διαρροή μνήμης (memory leak) — κατάσταση κατά την οποία ένα αντικείμενο που δεν χρειάζεται πλέον η εφαρμογή συνεχίζει να παραμένει στο heap επειδή υπάρχει ενεργή αναφορά από το root set (GC Root). Ο συλλέκτης σκουπιδιών θεωρεί ένα τέτοιο αντικείμενο ζωντανό και δεν το διαγράφει.

Φούσκωμα μνήμης (memory bloat) — ευρύτερο πρόβλημα όταν η εφαρμογή καταναλώνει περισσότερη μνήμη από όση χρειάζεται για την εκτέλεση των τρεχουσών εργασιών. Αιτίες: υπερβολική προσωρινή αποθήκευση, διπλασιασμός αντικειμένων, μη βέλτιστες δομές δεδομένων και κατακερματισμός heap.

Στο Android κάθε εφαρμογή λαμβάνει περιορισμένο heap (συνήθως 64-512 MB ανάλογα με τη συσκευή και την έκδοση OS). Στο iOS ο περιορισμός είναι λιγότερο αυστηρός, αλλά το σύστημα στέλνει προειδοποίηση μνήμης όταν πλησιάζει το όριο.

ΧαρακτηριστικόAndroidiOS
Όριο heap64-512 MB (ανάλογα με τη συσκευή)Σιωπηρό (σύστημα)
Συλλογή σκουπιδιώνART (Concurrent, Compact)ARC (Automatic Reference Counting)
Μηχανισμός διαρροήςΑναφορές GC RootRetain cycles (ισχυροί κύκλοι αναφορών)
ΑποτέλεσμαOutOfMemoryErrorΠροειδοποίηση μνήμης → τερματισμός

Σύμφωνα με το Facebook Engineering Blog, οι διαρροές μνήμης είναι η αιτία ~15% των αναφορών crash σε εφαρμογές κινητών. Στο Android προστίθενται ANR λόγω συχνών παύσεων GC όταν υπάρχει έλλειψη μνήμης.

Τυπικά μοτίβα διαρροών μνήμης σε Android και iOS

Στατική αναφορά σε Activity — κλασική διαρροή Android. Αν ένα στατικό πεδίο ή singleton αποθηκεύει αναφορά σε Activity, αυτή δεν θα συλλεγεί από τον GC ακόμα και μετά το finish(), όσο το singleton ζει. Το Activity είναι βαρύ αντικείμενο που περιέχει ιεραρχία View, πόρους και Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // διαρροή: στατική αναφορά σε Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ Η MainActivity δεν θα συλλεγεί ποτέ από τον GC
    }
}

Ανώνυμες κλάσεις και lambdas — σιωπηρά διατηρούν αναφορά στην εξωτερική κλάση. Αν ένα Runnable ή Callback μεταβιβαστεί σε εξωτερική υπηρεσία και η Activity καταστραφεί, το αντικείμενο της ανώνυμης κλάσης εξακολουθεί να κρέμεται στην ουρά και δεν επιτρέπει στην Activity να συλλεγεί από τον GC.

  • Handler με καθυστέρηση — αν η Activity έχει καταστραφεί αλλά το Handler.postDelayed δεν έχει ακόμα εκτελεστεί, η Activity διαρρέει
  • Thread και AsyncTask — κατά την περιστροφή οθόνης η Activity αναδημιουργείται, αλλά το παλιό Thread συνεχίζει να κρατά αναφορά στην παλιά Activity
  • Retrofit/Callback — ένα ανώνυμο Callback διατηρεί αναφορά στον presenter ή το fragment
  • Παρατηρητές (Observers) — συνδρομές LiveData ή RxJava χωρίς ακύρωση στο onDestroy

Στο iOS το κύριο πρόβλημα είναι οι retain cycles: δύο αντικείμενα διατηρούν ισχυρές αναφορές το ένα στο άλλο και η ARC δεν μπορεί να μηδενίσει τον μετρητή αναφορών για κανένα. Τυπική περίπτωση: ένα closure που συλλαμβάνει το self ισχυρά, και το self που κρατά αναφορά στο closure.

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

LeakCanary — βιβλιοθήκη από τη Square για αυτόματη ανίχνευση διαρροών στο Android. Μετά την καταστροφή Activity ή Fragment ελέγχει αν το αντικείμενο συλλέχθηκε από τον GC. Αν όχι — κάνει heap dump και εμφανίζει το ίχνος διαρροής.

kotlin
// LeakCanary 2.x — αυτόματη ενσωμάτωση μέσω Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // Το LeakCanary εγκαθίσταται αυτόματα σε debug build
        // μέσω ContentProvider — ρύθμιση με μηδενικό κώδικα
    }
}

// Αναγκαστική κλήση ελέγχου
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — το ενσωματωμένο εργαλείο για παρακολούθηση μνήμης σε πραγματικό χρόνο. Επιτρέπει εγγραφή heap dump, εύρεση ύποπτων αντικειμένων (Retained Size > 1 MB) και παρακολούθηση της διαδρομής GC root προς κάθε αντικείμενο.

Για iOS χρησιμοποιήστε το Xcode Memory Graph Debugger. Απεικονίζει το γράφημα αντικειμένων στη μνήμη, δείχνει retain cycles και επιτρέπει άμεσο εντοπισμό κυκλικών αναφορών. Επίσης διαθέσιμο είναι το Instruments > Allocations για μακροπρόθεσμη παρακολούθηση.

Στρατηγικές πρόληψης διαρροών

WeakReference — ο βασικός μηχανισμός για αναφορές που δεν πρέπει να εμποδίζουν τη συλλογή σκουπιδιών. Αν ο GC αποφασίσει να διαγράψει το αντικείμενο, η WeakReference επιστρέφει null. Χρησιμοποιείται για callbacks, listeners και αναφορές σε UI components από νήματα παρασκηνίου.

Lifecycle-aware components — η αρχιτεκτονική προσέγγιση που υλοποιείται στο Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Οι συνδρομές ακυρώνονται αυτόματα στο onDestroy, εξαλείφοντας την κύρια κατηγορία διαρροών.

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // το coroutine ακυρώνεται αυτόματα στο onCleared()
        }
    }
}

viewModelScope και lifecycleScope — ενσωματωμένα CoroutineScope στο Android που ακυρώνονται στο αντίστοιχο γεγονός κύκλου ζωής. Αυτό εξαλείφει τις διαρροές μέσω coroutines — το πιο συνηθισμένο σενάριο στη σύγχρονη ανάπτυξη Android.

  • Μην χρησιμοποιείτε στατικές αναφορές σε Context, Activity, View ή Fragment
  • Ακυρώνετε όλες τις συνδρομές RxJava στο disposeBag / CompositeDisposable στο onDestroy
  • Χρησιμοποιείτε [weak self] / [unowned self] στα closures του iOS για αποτροπή retain cycles
  • Ελέγχετε Bitmap και μεγάλα αντικείμενα — πρέπει να ανακυκλώνονται ή να μηδενίζονται

Εργαλεία προφίλ μνήμης

Memory Profiler in Android Studio — το κύριο εργαλείο για παρακολούθηση heap. Εμφανίζει live allocations, στιγμιότυπα heap, αριθμό αντικειμένων ανά τύπο. Επιτρέπει εγγραφή dump και ανάλυσή του στο MAT (Memory Analyzer Tool) για εύρεση ύποπτων αντικειμένων.

Eclipse MAT — επιτραπέζιος αναλυτής heap dump. Μετά τη φόρτωση του αρχείου HPROF από το Android Studio, το MAT χτίζει το dominator tree, δείχνει το retain size κάθε αντικειμένου και προσφέρει αυτόματη ανάλυση ύποπτων διαρροών μέσω του Leak Suspects Report.

Xcode Memory Graph — οπτικός εντοπιστής σφαλμάτων για retain cycles. Με το πάτημα του κουμπιού Memory Graph Debugger, το Xcode σταματά την εφαρμογή, χτίζει πλήρες γράφημα αντικειμένων στη μνήμη και επισημαίνει τα retain cycles με κόκκινο χρώμα.

ΕργαλείοΠλατφόρμαΧαρακτηριστικό
LeakCanaryAndroidΑυτόματη ανίχνευση διαρροής μετά από destroy
Memory ProfilerAndroid StudioHeap dump + live allocations
Eclipse MATAndroidDominator tree, Leak Suspects Report
Memory GraphiOS (Xcode)Οπτικοποιητής retain cycles

Σύμφωνα με το Google I/O 2023, οι εφαρμογές που χρησιμοποιούν LeakCanary σε debug builds μειώνουν τον αριθμό των crash που σχετίζονται με μνήμη κατά 30-50% τους πρώτους 2 μήνες μετά την εφαρμογή. Συνιστάται η προσθήκη του LeakCanary στη φάση ενσωμάτωσης του έργου.

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

Ποια είναι η διαφορά μεταξύ διαρροής μνήμης και φουσκώματος;

Διαρροή — αντικείμενα μη προσβάσιμα από τον κώδικα αλλά μη διαγραμμένα από τον GC λόγω ενεργών αναφορών. Φούσκωμα — η εφαρμογή διατηρεί στη μνήμη αντικείμενα που είναι λογικά απαραίτητα αλλά σε υπερβολική ποσότητα (π.χ. cache 50 MB σε μια εφαρμογή 80 MB). Το φούσκωμα αντιμετωπίζεται αρχιτεκτονικά, η διαρροή — μέσω σωστής διαχείρισης αναφορών.

Πώς βρίσκει διαρροές το LeakCanary;

LeakCanary χρησιμοποιεί ObjectWatcher — μετά το onDestroy() μιας Activity δημιουργεί WeakReference στην Activity και εκκινεί τον GC. Αν μετά από 5 δευτερόλεπτα η WeakReference δεν έχει καθαριστεί, το LeakCanary κάνει heap dump, αναλύει τη συντομότερη αλυσίδα αναφορών από GC Root στο αντικείμενο και εμφανίζει το ακριβές stack διαρροής με όνομα αρχείου και γραμμή κώδικα.

Γιατί το Bitmap προκαλεί συχνά OutOfMemoryError;

Bitmap καταλαμβάνει μνήμη εκτός του Java heap στην native μνήμη (native heap). Μέγεθος ενός Bitmap = πλάτος × ύψος × 4 bytes (ARGB_8888). Μια φωτογραφία 12 MP (4000×3000) καταλαμβάνει 48 MB. Το Android δεν μπορεί πάντα να ελευθερώσει έγκαιρα τη native μνήμη, που όταν συσσωρεύονται πολλά Bitmaps οδηγεί σε OOM ακόμα και με επαρκή Java heap.

Τι είναι το retain cycle στο iOS;

Retain cycle — κατάσταση στο ARC όπου δύο αντικείμενα διατηρούν ισχυρές αναφορές το ένα στο άλλο και ο μετρητής αναφορών δεν φτάνει ποτέ στο μηδέν. Τυπικό παράδειγμα: ViewController με ισχυρή αναφορά σε closure, και το closure συλλαμβάνει ισχυρά το self. Λύση: χρησιμοποιήστε [weak self] ή [unowned self] στα closures.

Ποιο είναι το μέγιστο μέγεθος heap στο Android;

Το μέγεθος heap εξαρτάται από τη συσκευή και την έκδοση Android. Για παλιές συσκευές (API 15-24) — 64-128 MB. Για σύγχρονες (API 25+) — 256-512 MB. Η ακριβής τιμή μπορεί να ληφθεί μέσω ActivityManager.getMemoryClass(). Για μεγάλες εφαρμογές (παιχνίδια, επεξεργαστές) υπάρχει largeHeap=true στο manifest, που δίνει έως 1 GB.

Περίληψη

  • Διαρροή μνήμης — αντικείμενο που δεν διαγράφεται από τον GC λόγω ενεργής αναφοράς από το root set; φούσκωμα — υπερβολική κατανάλωση μνήμης χωρίς εμφανείς διαρροές
  • Στατικές αναφορές σε Activity, Context, View — νούμερο ένα αιτία διαρροών στο Android; λύση — WeakReference ή Application Context
  • Ανώνυμες κλάσεις και lambdas σιωπηρά διατηρούν αναφορά στην εξωτερική κλάση; μη ακυρωμένα callbacks — δεύτερη πιο συνηθισμένη αιτία
  • LeakCanary — το πρότυπο αυτόματης ανίχνευσης διαρροών στο Android; η ενσωμάτωση διαρκεί 5 λεπτά και μειώνει το ποσοστό crash κατά 30-50%
  • lifecycleScope και viewModelScope ακυρώνουν αυτόματα τα coroutines στο destroy, εξαλείφοντας μια ολόκληρη κατηγορία διαρροών
  • Retain cycles στο iOS επιλύονται με weak/unowned self σε closures και delegates
  • Κάντε προφίλ μνήμης τουλάχιστον μία φορά ανά sprint — το heap dump με MAT ή Memory Graph πρέπει να γίνει μέρος του code review

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

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

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

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