OutOfMemoryError — θανατηφόρα εξαίρεση που προκύπτει όταν η Java Virtual Machine (JVM) ή το Android Runtime (ART) δεν μπορεί να διαθέσει μνήμη για ένα νέο αντικείμενο λόγω έλλειψης χώρου στο Heap. Σύμφωνα με το Square Engineering, το 70% των OutOfMemoryError σε κινητές εφαρμογές προκαλείται από διαρροές μνήμης, όχι από πραγματική υπέρβαση ορίου. Η κατανόηση των αιτιών OOM είναι το κλειδί για σταθερή λειτουργία της εφαρμογής.
Βασικά σημεία
OutOfMemoryError (OOM) — είναι μια εξαίρεση από την οικογένεια VirtualMachineError σε Java/Kotlin που σηματοδοτεί την αδυναμία διάθεσης μνήμης για ένα νέο αντικείμενο. Σε αντίθεση με τις checked εξαιρέσεις, το OOM είναι Error και δεν απαιτεί χειρισμό μέσω catch — αν και τεχνικά μπορεί να πιαστεί. Μετά την εμφάνιση OOM, η εφαρμογή βρίσκεται συνήθως σε ασταθή κατάσταση και συνιστάται το κλείσιμό της.
Σε Android, κάθε εφαρμογή έχει ένα όριο Heap που ορίζεται από τον κατασκευαστή της συσκευής. Για σύγχρονα smartphone με 6+ GB RAM, το όριο είναι 256–512 MB, για οικονομικές συσκευές — 128–192 MB. Όταν ο συνολικός όγκος όλων των ζώντων αντικειμένων υπερβαίνει αυτό το όριο, το ART πετάει OutOfMemoryError.
Σημαντικό να καταλάβετε: το OOM δεν σημαίνει πάντα ότι η φυσική μνήμη της συσκευής εξαντλήθηκε. Σημαίνει ότι η εφαρμογή έχει εξαντλήσει το όριο Heap που ορίζει το σύστημα. Άλλες εφαρμογές μπορεί να έχουν διαθέσιμη μνήμη, αλλά η εφαρμογή σας δεν μπορεί να τη χρησιμοποιήσει λόγω της απομόνωσης διεργασιών σε Android.
Πέντε σενάρια οδηγούν τακτικά σε OOM σε κινητές εφαρμογές. Κάθε σενάριο σχετίζεται με έναν συγκεκριμένο τύπο δεδομένων ή λειτουργία.
Bitmap — ο κύριος καταναλωτής μνήμης σε εφαρμογές Android. Η φόρτωση μιας εικόνας FullHD (1920 × 1080) σε αρχικό μέγεθος καταλαμβάνει 8.3 MB σε μορφή ARGB_8888. Εάν υπάρχουν 50 τέτοιες εικόνες σε ένα RecyclerView — αυτό είναι 415 MB, που υπερβαίνει το Heap οποιασδήποτε συσκευής. Η φόρτωση εικόνων χωρίς inSampleSize — εγγυημένο OOM σε αδύναμες συσκευές.
Χρησιμοποιήστε Glide ή Coil για αυτόματη κλίμακα. Αυτές οι βιβλιοθήκες φορτώνουν εικόνες σε μέγεθος που ταιριάζει με το View, όχι στην αρχική ανάλυση. Για άμεση χρήση BitmapFactory.Options, εφαρμόστε inSampleSize: υπολογίστε το ως δύναμη του δύο έτσι ώστε το τελικό μέγεθος να μην υπερβαίνει τα 2048 × 2048 pixel. Επιπλέον, χρησιμοποιήστε RGB_565 αντί για ARGB_8888 για εικόνες χωρίς διαφάνεια — αυτό μειώνει την κατανάλωση μνήμης κατά το ήμισυ.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
Μία διαρροή λίγων KB δεν θα προκαλέσει OOM. Αλλά δεκάδες διαρροές σε κάθε οθόνη συσσωρεύονται: κάθε μετάβαση σε μια οθόνη προσθέτει μια διαρροή, το GC δεν μπορεί να απελευθερώσει τα αντικείμενα, το Heap γεμίζει. Τυπικό μοτίβο: ο χρήστης ανοίγει και κλείνει την οθόνη προφίλ 20 φορές → το Heap αυξάνεται κατά 200 MB → η εφαρμογή καταρρέει με OOM.
Εγκαταστήστε το LeakCanary στο έργο για αυτόματη ανίχνευση διαρροών. Θα εμφανίσει κάθε αντικείμενο που διαρρέει με ακριβές stack trace. Μετά την επισκευή όλων των διαρροών, η κατανάλωση Heap γίνεται σταθερή: μετά το κλείσιμο της οθόνης, η μνήμη επιστρέφει στο βασικό επίπεδο.
Η φόρτωση αρχείων στο σύνολό τους σε byte[] — ο άμεσος δρόμος προς OOM. Ένα JSON αρχείο 50 MB κατά την ανάλυση θα δημιουργήσει ένα string ίδιου μεγέθους συν το μοντέλο DOM. Τα αρχεία βίντεο που φορτώνονται στη μνήμη, οι προφυλακτικές audio και τα μεγάλα σύνολα δεδομένων protobuf — όλα μπορούν να υπερβούν το όριο Heap σε μία μόνο λειτουργία.
Επεξεργαστείτε μεγάλα δεδομένα με ροή: InputStream με buffer 4–8 KB, streaming JSON parser (Jackson ή Gson με JsonReader), MediaCodec για βίντεο. Ποτέ μην καλείτε File.readBytes() σε αρχεία μεγαλύτερα από 10% του διαθέσιμου Heap.
Η εντατική δημιουργία αντικειμένων σε βρόχο χωρίς ενδιάμεσο GC μπορεί να οδηγήσει σε OOM, ιδιαίτερα σε συσκευές με μικρό Heap. Παράδειγμα: η δημιουργία 100.000 αντικειμένων σε έναν βρόχο for που δεν χωρούν στο Heap πριν το GC προλάβει να τα συλλέξει. Αυτό συμβαίνει συχνότερα σε παιχνίδια και γραφικούς επεξεργαστές.
Χρησιμοποιήστε Object Pool για αντικείμενα που δημιουργούνται και καταστρέφονται μαζικά. Για αριθμητικά δεδομένα, χρησιμοποιήστε πρωτόγονους τύπους (FloatArray αντί List<Float>). RecyclerView με ViewHolder Pool λύνει αυτό το πρόβλημα για τα UI components.
Κατακερματισμός — κατάσταση κατά την οποία υπάρχει συνολικά αρκετή ελεύθερη μνήμη, αλλά δεν υπάρχει συνεχές μπλοκ για ένα νέο αντικείμενο. Το ART συμπυκνώνει το Heap κατά τη διάρκεια GC, αλλά όχι πάντα με επιτυχία. Οι μεγάλοι πίνακες (Bitmap, byte[]) είναι οι πιο ευαίσθητοι σε κατακερματισμό.
Το ART σε Android 8+ χρησιμοποιεί Generational GC, που μειώνει τον κατακερματισμό διαχωρίζοντας τα νέα και παλιά αντικείμενα. Παρόλα αυτά, αποφύγετε τη διάθεση τμημάτων διαφορετικού μεγέθους σε ένα pool — προσπαθήστε να χρησιμοποιείτε προ-διατεθειμένα buffer σταθερού μεγέθους.
Το όριο Heap σε Android δεν είναι σταθερό — εξαρτάται από τον κατασκευαστή, το μοντέλο της συσκευής και την έκδοση OS. Η Google καθορίζει ελάχιστες απαιτήσεις μέσω Compatibility Definition Document (CDD), αλλά οι κατασκευαστές ορίζουν τις πραγματικές τιμές.
| Κατηγορία συσκευών | Τυπικό Heap | largeHeap |
|---|---|---|
| Οικονομικές (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Μεσαίες (3–4 GB RAM) | 256–384 MB | 512 MB |
| Κορυφαίες (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tablet (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | Όχι |
Μπορείτε να ζητήσετε αυξημένο όριο μέσω android:largeHeap="true" στο manifest. Χρησιμοποιήστε το με προσοχή: η αύξηση Heap δεν λύνει το πρόβλημα των διαρροών και μπορεί να χειροτερέψει την εμπειρία χρήστη αν το σύστημα αναγκαστεί να τερματίσει άλλες εφαρμογές για να ελευθερώσει μνήμη. Για Wear OS, το όριο Heap είναι ελάχιστο — μόνο 32–64 MB, εδώ το largeHeap δεν είναι διαθέσιμο και η εξοικονόμηση μνήμης είναι διπλά κρίσιμη.
Η διάγνωση OOM απαιτεί ανάλυση Heap Dump και κατανόηση ποια αντικείμενα καταναλώνουν μνήμη. Το Android Studio παρέχει όλα τα απαραίτητα εργαλεία.
Βήμα 1: Πιάστε τη στιγμή OOM. Στο Android Memory Profiler, πατήστε Record memory allocations και εκτελέστε το σενάριο που προκαλεί κατάρρευση. Το profiler θα δείξει άλμα διαθέσεων πριν το OOM. Αν το OOM δεν αναπαράγεται, μειώστε το Heap μέσω android:smallHeap σε debug έκδοση ή χρησιμοποιήστε DDMS με χειροκίνητη κλήση GC.
Βήμα 2: Λάβετε Heap Dump τη στιγμή της αιχμής φόρτωσης (πριν το OOM). Ανοίξτε το Dump στο Android Studio: η καρτέλα Classes είναι ταξινομημένη κατά Retained Size. Τα μεγαλύτερα αντικείμενα — Bitmap, byte[], String. Για κάθε Bitmap, ελέγξτε το μέγεθος (width × height × 4 bytes) και τη διαδρομή φόρτωσης μέσω Stack Trace.
Βήμα 3: Αναλύστε τον αριθμό των επαναλαμβανόμενων αντικειμένων. Αν βλέπετε 200 ίδια Fragment ή Activity — αυτό είναι διαρροή. Αν 500 Bitmap ίδιου μεγέθους — πρόβλημα με την προσωρινή μνήμη εικόνων. Το MAT (Memory Analyzer Tool) παρέχει βαθύτερη ανάλυση με Dominator Tree, δείχνοντας ποια αντικείμενα κρατούν 80% του Heap.
// Εντολή για Heap Dump μέσω adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
Μια ολοκληρωμένη στρατηγική πρόληψης OOM περιλαμβάνει πέντε επίπεδα προστασίας: από αρχιτεκτονικές λύσεις έως παρακολούθηση σε παραγωγή.
ViewModel + Repository μοτίβο διαχωρίζει τα δεδομένα από το UI και αποτρέπει την κατάσχεση View κατά την περιστροφή οθόνης. Το ViewModel επιζεί περισσότερο από το Activity, τα δεδομένα του δεν χάνονται και το View μπορεί να αναδημιουργηθεί χωρίς διπλασιασμό δεδομένων στη μνήμη. Χρησιμοποιήστε StateFlow αντί LiveData για ρητή διαχείριση καταστάσεων.
Glide — υποχρεωτική βιβλιοθήκη για εργασία με εικόνες. Κλιμακώνει αυτόματα, προσωρινά αποθηκεύει (δίσκος + μνήμη) και ανακυκλώνει Bitmap. Διαμορφώστε diskCacheStrategy και skipMemoryCache για μεγάλες λίστες. Για κινούμενες εικόνες, χρησιμοποιήστε Glide με GIF/WebP — καταλαμβάνουν λιγότερη μνήμη από μια ακολουθία Bitmap.
Firebase Performance Monitoring παρακολουθεί την κατανάλωση μνήμης σε πραγματικό χρόνο. Ορίστε ειδοποίηση όταν το Heap είναι πάνω από 80% του ορίου — αυτό είναι σήμα για έλεγχο. Το Crashlytics συλλέγει το OOM ως εξαίρεση και δείχνει την τελευταία γνωστή κατάσταση Heap πριν την κατάρρευση. Για Android 11+, χρησιμοποιήστε ApplicationExitInfo για ανίχνευση τερματισμού OOM.
Υποχρεωτικά δοκιμάστε την εφαρμογή σε συσκευές με ελάχιστο Heap (128–192 MB). Ένας εξομοιωτής με μικρή οθόνη και μικρό Heap προσομοιώνει μια οικονομική συσκευή. Αν η εφαρμογή λειτουργεί σε τέτοια συσκευή, στις κορυφαίες συσκευές δεν θα υπάρχουν προβλήματα OOM. Χρησιμοποιήστε Firebase Test Lab με πραγματικές συσκευές από διαφορετικές κατηγορίες τιμής.
// Έλεγχος διαθέσιμου Heap πριν από βαριά λειτουργία
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // απόθεμα 50%
}
Συχνές Ερωτήσεις
Τεχνικά ναι, αλλά δεν συνιστάται. Μετά το OOM, η εφαρμογή βρίσκεται σε ασταθή κατάσταση: νέες διαθέσεις μπορεί να μην λειτουργούν και κάποια αντικείμενα μπορεί να είναι μερικώς δημιουργημένα. Η μόνη λογική ενέργεια σε catch — καταγραφή και επανεκκίνηση Activity.
Το όριο Heap είναι διαφορετικό σε διαφορετικές συσκευές. Μια λειτουργία που απαιτεί 300 MB θα καταρρεύσει σε μια συσκευή με όριο 192 MB, αλλά θα λειτουργήσει σε μια κορυφαία με 512 MB. Δοκιμάστε σε συσκευές με ελάχιστες προδιαγραφές για να ανακαλύψετε σενάρια OOM.
largeHeap αυξάνει το όριο, αλλά δεν επιταχύνει την εφαρμογή. Οι παύσεις GC γίνονται μεγαλύτερες επειδή η συλλογή ενός μεγάλου Heap διαρκεί περισσότερο. Το σύστημα μπορεί να τερματίσει εφαρμογές φόντου για να εξασφαλίσει μνήμη. Χρησιμοποιήστε largeHeap μόνο για εφαρμογές που αντικειμενικά χρειάζονται πολλή μνήμη (κάμερες, επεξεργαστές).
OOM — εξαίρεση εντός της εφαρμογής όταν υπάρχει έλλειψη Heap. Ο τερματισμός από το σύστημα (Low Memory Killer) — απόφαση του πυρήνα Linux να τερματίσει μια διεργασία για να ελευθερώσει μνήμη για άλλες εφαρμογές. Στον τερματισμό από το σύστημα, η εφαρμογή δεν λαμβάνει εξαίρεση — η διεργασία απλά τερματίζεται.
Τύπος: width × height × bytesPerPixel. ARGB_8888 = 4 B/pixel, RGB_565 = 2 B/pixel. FullHD Bitmap (1920 × 1080) σε ARGB_8888 = 8.3 MB. 4K Bitmap (3840 × 2160) = 33 MB. Πάντα κλιμακώστε τις εικόνες στο μέγεθος που απαιτείται για εμφάνιση στην οθόνη.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης