OutOfMemoryError w tworzeniu aplikacji: co to jest, przyczyny i metody zapobiegania

Autor: IT Sectr Opublikowano: 2026-03-29 Czas czytania: 9 min

OutOfMemoryError — fatalny wyjątek, który pojawia się, gdy maszyna wirtualna Java (JVM) lub Android Runtime (ART) nie może przydzielić pamięci dla nowego obiektu z powodu braku miejsca na stercie (Heap). Według Square Engineering, 70% OutOfMemoryError w aplikacjach mobilnych jest spowodowanych wyciekami pamięci, a nie rzeczywistym przekroczeniem limitu. Zrozumienie przyczyn OOM to klucz do stabilnej pracy aplikacji.

Najważniejsze

  • OutOfMemoryError — wyjątek przy braku miejsca na stercie dla nowego obiektu
  • Heap (sterta) — obszar pamięci, w którym żyją wszystkie obiekty Java/Kotlin
  • Bitmap — główny konsument pamięci Heap w Androidzie, typowe źródło OOM
  • Heap Dump — zrzut sterty do analizy, kto i ile pamięci zajmuje
  • Leczenie OOM wymaga usunięcia wycieków i optymalizacji zużycia pamięci

Czym jest OutOfMemoryError

OutOfMemoryError (OOM) — to wyjątek z rodziny VirtualMachineError w Java/Kotlin, który sygnalizuje niemożność przydzielenia pamięci dla nowego obiektu. W przeciwieństwie do wyjątków checked, OOM jest błędem (Error) i nie wymaga obsługi przez catch — choć technicznie można go złapać. Po wystąpieniu OOM aplikacja zwykle znajduje się w niestabilnym stanie i zaleca się jej zamknięcie.

Na Androidzie każda aplikacja ma limit Heap ustawiony przez producenta urządzenia. Dla nowoczesnych smartfonów z 6+ GB RAM limit wynosi 256–512 MB, dla budżetowych urządzeń — 128–192 MB. Gdy łączny rozmiar wszystkich żywych obiektów przekracza ten limit, ART wyrzuca OutOfMemoryError.

Ważne jest zrozumienie: OOM nie zawsze oznacza, że w urządzeniu skończyła się fizyczna pamięć. Oznacza to, że aplikacja wyczerpała swój limit Heap ustawiony przez system. Inne aplikacje mogą mieć wolną pamięć, ale twoja aplikacja nie może jej użyć z powodu izolacji procesów w Androidzie.

Główne przyczyny OutOfMemoryError

Pięć scenariuszy regularnie prowadzi do OOM w aplikacjach mobilnych. Każdy scenariusz jest związany z określonym typem danych lub operacją.

Bitmap bez skalowania

Bitmap — główny konsument pamięci w aplikacjach Android. Załadowanie obrazu FullHD (1920 × 1080) w oryginalnym rozmiarze zajmuje 8.3 MB w formacie ARGB_8888. Jeśli w RecyclerView jest 50 takich obrazów — to 415 MB, co przekracza Heap każdego urządzenia. Ładowanie obrazów bez inSampleSize — gwarantowany OOM na słabych urządzeniach.

Używaj Glide lub Coil do automatycznego skalowania. Te biblioteki ładują obrazy w rozmiarze dopasowanym do View, a nie do oryginalnej rozdzielczości. Do bezpośredniego użycia BitmapFactory.Options stosuj inSampleSize: oblicz go jako potęgę dwójki, tak aby końcowy rozmiar nie przekraczał 2048 × 2048 pikseli. Dodatkowo używaj RGB_565 zamiast ARGB_8888 dla obrazów bez przezroczystości — to dwukrotnie zmniejsza zużycie pamięci.

kotlin
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)
}

Wycieki pamięci (narastanie)

Jeden wyciek kilku KB nie spowoduje OOM. Ale dziesiątki wycieków na każdym ekranie narastają: każde przejście na ekran dodaje wyciek, GC nie może zwolnić obiektów, Heap się zapełnia. Typowy wzorzec: użytkownik otwiera i zamyka ekran profilu 20 razy → Heap rośnie o 200 MB → aplikacja pada z OOM.

Zainstaluj LeakCanary w projekcie do automatycznego wykrywania wycieków. Pokaże on każdy wyciekający obiekt z dokładnym stack trace. Po naprawieniu wszystkich wycieków zużycie Heap stanie się stabilne: po zamknięciu ekranu pamięć wraca do podstawowego poziomu.

Duże pliki w pamięci

Ładowanie plików w całości do byte[] — prosta droga do OOM. Plik JSON o rozmiarze 50 MB podczas parsowania utworzy string tego samego rozmiaru plus model DOM. Pliki wideo załadowane do pamięci, bufory audio i duże zbiory danych protobuf — wszystkie mogą przekroczyć limit Heap w jednej operacji.

Przetwarzaj duże dane strumieniami: InputStream z buforem 4–8 KB, Streaming JSON- parser (Jackson lub Gson z JsonReader), MediaCodec dla wideo. Nigdy nie wywołuj File.readBytes() na plikach większych niż 10% dostępnego Heap.

Tworzenie wielu obiektów w pętli

Intensywne tworzenie obiektów w pętli bez pośredniego GC może prowadzić do OOM, szczególnie na urządzeniach z małym Heap. Przykład: generowanie 100 000 obiektów w pętli for, które nie mieszczą się w Heap zanim GC zdąży je zebrać. Częściej występuje w grach i edytorach graficznych.

Używaj Object Pool dla obiektów, które są masowo tworzone i niszczone. Dla danych numerycznych używaj typów prymitywnych (FloatArray zamiast List<Float>). RecyclerView z ViewHolder Pool rozwiązuje ten problem dla komponentów UI.

Fragmentacja Heap

Fragmentacja — stan, w którym wolnej pamięci jest łącznie wystarczająco, ale nie ma ciągłego bloku dla nowego obiektu. ART kompaktuje Heap podczas GC, ale nie zawsze z powodzeniem. Duże tablice (Bitmap, byte[]) są najbardziej wrażliwe na fragmentację.

ART na Androidzie 8+ używa Generational GC, który zmniejsza fragmentację poprzez podział na młode i stare obiekty. Mimo to, unikaj alokowania fragmentów różnych rozmiarów w jednej puli — staraj się używać pre-allocated buforów o stałym rozmiarze.

Limity Heap w Androidzie

Limit Heap w Androidzie nie jest stałą — zależy od producenta, modelu urządzenia i wersji OS. Google określa minimalne wymagania poprzez Compatibility Definition Document (CDD), ale producenci ustalają rzeczywiste wartości.

Kategoria urządzeńTypowy HeaplargeHeap
Budżetowe (1–2 GB RAM)128–192 MB256–384 MB
Średnie (3–4 GB RAM)256–384 MB512 MB
Flagowe (6+ GB RAM)384–512 MB768 MB–1 GB
Tablety (4+ GB RAM)256–512 MB768 MB
Wear OS32–64 MBBrak

Można poprosić o zwiększony limit przez android:largeHeap="true" w manifeście. Używaj go ostrożnie: zwiększenie Heap nie rozwiązuje problemu wycieków i może pogorszyć doświadczenie użytkownika, jeśli system będzie zmuszony zabijać inne aplikacje, aby zwolnić pamięć dla twojej. Dla Wear OS limit Heap jest minimalny — zaledwie 32–64 MB, tutaj largeHeap jest niedostępny, a oszczędność pamięci jest podwójnie krytyczna.

Diagnostyka OutOfMemoryError

Diagnostyka OOM wymaga analizy Heap Dump i zrozumienia, które obiekty zużywają pamięć. Android Studio dostarcza wszystkie niezbędne narzędzia.

Krok 1: Złap moment OOM. W Android Memory Profiler naciśnij Record memory allocations i wykonaj scenariusz, który powoduje crash. Profiler pokaże skok alokacji przed OOM. Jeśli OOM się nie odtwarza, zmniejsz Heap przez android:smallHeap w debug-buildzie lub użyj DDMS z ręcznym wywołaniem GC.

Krok 2: Wykonaj zrzut Heap Dump w momencie szczytowego obciążenia (przed OOM). Otwórz Dump w Android Studio: zakładka Classes posortowana według Retained Size. Największe obiekty — Bitmap, byte[], String. Dla każdego Bitmap sprawdź rozmiar (width × height × 4 bajty) i ścieżkę ładowania przez Stack Trace.

Krok 3: Analizuj liczbę powtarzających się obiektów. Jeśli widzisz 200 identycznych Fragment lub Activity — to wyciek. Jeśli 500 Bitmap o tym samym rozmiarze — problem z buforowaniem obrazów. MAT (Memory Analyzer Tool) zapewnia głębszą analizę z Dominator Tree, pokazującą, które obiekty utrzymują 80% Heap.

text
// Polecenie do zrzutu Heap Dump przez adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.

Strategie zapobiegania OOM

Kompleksowa strategia zapobiegania OOM obejmuje pięć poziomów ochrony: od rozwiązań architektonicznych po monitoring na produkcji.

Rozwiązania architektoniczne

ViewModel + Repository wzorzec oddziela dane od UI i zapobiega utrzymywaniu View przy obrocie ekranu. ViewModel przetrwa Activity, jego dane nie są tracone, a View może być odtworzone bez duplikowania danych w pamięci. Używaj StateFlow zamiast LiveData do jawnego zarządzania stanami.

Zarządzanie Bitmap i obrazami

Glide — obowiązkowa biblioteka do pracy z obrazami. Automatycznie skaluje, buforuje (dysk + pamięć) i recycluje Bitmap. Skonfiguruj diskCacheStrategy i skipMemoryCache dla dużych list. Dla animowanych obrazów używaj Glide z GIF/WebP — zajmują mniej pamięci niż sekwencja Bitmap.

Monitoring na produkcji

Firebase Performance Monitoring śledzi zużycie pamięci w czasie rzeczywistym. Ustaw alert na zajęcie Heap powyżej 80% limitu — to sygnał do sprawdzenia. Crashlytics zbiera OOM jako wyjątek i pokazuje ostatni znany stan Heap przed crashem. Dla Android 11+ używaj ApplicationExitInfo do detekcji zakończeń OOM.

Testowanie na słabych urządzeniach

Obowiązkowo testuj aplikację na urządzeniach z minimalnym Heap (128–192 MB). Emulator z małym ekranem i małym Heap emuluje budżetowe urządzenie. Jeśli aplikacja działa na takim urządzeniu, na flagowcach problemów z OOM nie będzie. Używaj Firebase Test Lab z rzeczywistymi urządzeniami różnych kategorii cenowych.

kotlin
// Sprawdzenie dostępnego Heap przed ciężką operacją
fun canAllocate(requiredBytes: Long): Boolean {
    val runtime = Runtime.getRuntime()
    val free = runtime.freeMemory()
    return free > requiredBytes * 2 // zapas 50%
}

Często zadawane pytania

Czy można złapać OutOfMemoryError przez try-catch?

Technicznie tak, ale nie jest to zalecane. Po OOM aplikacja znajduje się w niestabilnym stanie: nowe alokacje mogą nie działać, a niektóre obiekty mogą być częściowo utworzone. Jedyną rozsądną akcją w catch jest logowanie i restart Activity.

Dlaczego OOM występuje nie na wszystkich urządzeniach?

Limit Heap jest różny na różnych urządzeniach. Operacja, która wymaga 300 MB, upadnie na urządzeniu z limitem 192 MB, ale przejdzie na flagowcu z 512 MB. Testuj na urządzeniach z minimalnymi parametrami, aby wykryć scenariusze OOM.

Jak largeHeap wpływa na wydajność?

largeHeap zwiększa limit, ale nie przyspiesza aplikacji. Pauzy GC stają się dłuższe, ponieważ zbieranie dużego Heap trwa dłużej. System może zabijać aplikacje w tle, aby zapewnić pamięć. Używaj largeHeap tylko dla aplikacji, które obiektywnie potrzebują dużo pamięci (kamery, edytory).

Czym różni się OOM od systemowego zabicia procesu?

OOM — wyjątek wewnątrz aplikacji przy braku Heap. Systemowe zabicie (Low Memory Killer) — decyzja jądra Linux o zabiciu procesu w celu zwolnienia pamięci dla innych aplikacji. Przy systemowym zabiciu aplikacja nie otrzymuje wyjątku — proces po prostu się kończy.

Ile pamięci naprawdę zajmuje Bitmap?

Wzór: width × height × bytesPerPixel. ARGB_8888 = 4 B/piksel, RGB_565 = 2 B/piksel. FullHD Bitmap (1920 × 1080) w ARGB_8888 = 8.3 MB. 4K Bitmap (3840 × 2160) = 33 MB. Zawsze skaluj obrazy do rozmiaru niezbędnego do wyświetlenia na ekranie.

Podsumowanie

  • OutOfMemoryError — fatalny wyjątek przy wyczerpaniu limitu Heap aplikacji
  • Bitmap bez skalowania — główny winowajca OOM w aplikacjach mobilnych
  • Wycieki pamięci powodują 70% OOM przez narastanie obiektów przy każdej nawigacji
  • Limit Heap waha się od 128 MB na budżetowych do 512 MB na flagowych urządzeniach
  • Heap Dump z analizą Retained Size — główne narzędzie diagnostyki OOM
  • Glide lub Coil są obowiązkowe do pracy z obrazami dowolnego rozmiaru
  • Testowanie na urządzeniach z minimalnym Heap — must-have dla wszystkich projektów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również