Zżera pamięć i puchnie — co to jest, przyczyny i jak uniknąć

Autor: IT Sectr Opublikowano: 2026-07-28 Czas czytania: 10 min

Wyciek pamięci — jeden z najbardziej podstępnych problemów w programowaniu mobilnym. Pamięć aplikacji nieustannie rośnie, aż osiąga limit ustawiony przez system operacyjny, po czym następuje OutOfMemoryError lub wymuszone zakończenie. Według Square Engineering około 40% aplikacji Android ma co najmniej jeden wyciek pamięci, który można wykryć tylko podczas profilowania. Omówimy przyczyny i metody zapobiegania wzrostowi pamięci.

Najważniejsze

  • GC reachability — obiekt nie jest usuwany, jeśli istnieje aktywna referencja z zestawu korzeniowego
  • Statyczne referencje do Activity lub Context — najczęstsza przyczyna wycieku w Androidzie
  • LeakCanary — standardowe narzędzie do automatycznego wykrywania wycieków w Androidzie
  • WeakReference — rozwiązanie dla referencji, które nie powinny blokować garbage collectora
  • Komponenty Lifecycle-aware automatycznie anulują subskrypcje przy zniszczeniu widoku

Czym jest wyciek pamięci i rozdymanie aplikacji?

Wyciek pamięci (memory leak) — sytuacja, w której obiekt, który nie jest już potrzebny aplikacji, nadal pozostaje w stercie, ponieważ istnieje aktywna referencja z zestawu korzeniowego (GC Root). Garbage collector uznaje taki obiekt za żywy i nie usuwa go.

Rozdymanie pamięci (memory bloat) — szerszy problem, gdy aplikacja zużywa więcej pamięci niż potrzeba do wykonania bieżących zadań. Przyczyny: nadmierne buforowanie, duplikowanie obiektów, nieoptymalne struktury danych i fragmentacja sterty.

W Androidzie każda aplikacja ma ograniczoną stertę (zwykle 64-512 MB w zależności od urządzenia i wersji systemu). W iOS ograniczenie jest mniej restrykcyjne, ale system wysyła ostrzeżenie o pamięci przy zbliżaniu się do limitu.

CechaAndroidiOS
Limit sterty64-512 MB (zależy od urządzenia)Niejawny (systemowy)
Garbage collectorART (Concurrent, Compact)ARC (Automatic Reference Counting)
Mechanizm wyciekuReferencje GC RootCykle retain (silne cykle referencji)
SkutekOutOfMemoryErrorOstrzeżenie o pamięci → zakończenie

Według Facebook Engineering Blog, wycieki pamięci są przyczyną ~15% raportów crash w aplikacjach mobilnych. W Androidzie dochodzą do tego ANR spowodowane częstymi pauzami GC przy braku pamięci.

Typowe wzorce wycieków pamięci w Android i iOS

Statyczna referencja do Activity — klasyka wycieków w Androidzie. Jeśli statyczne pole lub singleton przechowuje referencję do Activity, nie zostanie ona zebrana przez GC nawet po finish(), dopóki singleton żyje. Activity to ciężki obiekt zawierający hierarchię widoków, zasoby i Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // przeciek: statyczna referencja do Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity nigdy nie zostanie zebrane przez GC
    }
}

Klasy anonimowe i lambdy — niejawnie przechowują referencję do klasy zewnętrznej. Jeśli Runnable lub Callback jest przekazany do zewnętrznego serwisu, a Activity jest niszczone, obiekt klasy anonimowej nadal wisi w kolejce i nie pozwala Activity zostać zebranym przez GC.

  • Handler z opóźnieniem — jeśli Activity jest zniszczone, a Handler.postDelayed jeszcze nie został wykonany, Activity wycieka
  • Thread i AsyncTask — przy obrocie ekranu Activity jest odtwarzane, a stary Thread nadal trzyma referencję do starego Activity
  • Retrofit/Callback — anonimowy Callback przechowuje referencję do presentera lub fragmentu
  • Obserwatorzy (Observers) — subskrypcje LiveData lub RxJava bez anulowania przy onDestroy

W iOS głównym problemem są retain cycles: dwa obiekty przechowują silne referencje wzajemnie, a ARC nie może wyzerować licznika referencji dla żadnego z nich. Typowy przypadek: closure przechwytujący self silnie, a self przechowujący referencję do closure.

Jak wykrywać wycieki pamięci?

LeakCanary — biblioteka od Square do automatycznego wykrywania wycieków w Androidzie. Po zniszczeniu Activity lub Fragment sprawdza, czy obiekt został zebrany przez GC. Jeśli nie — wykonuje heap dump i pokazuje trace wycieku.

kotlin
// LeakCanary 2.x — auto-integracja przez Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary instaluje się automatycznie w debug build
        // przez ContentProvider — konfiguracja zerowym kodem
    }
}

// Wymuszenie sprawdzenia
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — wbudowane narzędzie do monitorowania pamięci w czasie rzeczywistym. Pozwala zapisać heap dump, znaleźć podejrzane obiekty (Retained Size > 1 MB) i prześledzić ścieżkę GC root do każdego obiektu.

Dla iOS używaj Xcode Memory Graph Debugger. Wizualizuje on graf obiektów w pamięci, pokazuje retain cycles i pozwala błyskawicznie wykryć cykliczne referencje. Dostępny jest również Instruments > Allocations do długoterminowego monitorowania.

Strategie zapobiegania wyciekom

WeakReference — podstawowy mechanizm dla referencji, które nie powinny blokować garbage collectora. Jeśli GC zdecyduje się usunąć obiekt, WeakReference zwróci null. Używane do callbacków, słuchaczy i referencji do komponentów UI z wątków tła.

Komponenty Lifecycle-aware — podejście architektoniczne zaimplementowane w Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Subskrypcje są automatycznie anulowane przy onDestroy, co eliminuje główną klasę wycieków.

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)
            // korutyna auto-anuluje się przy onCleared()
        }
    }
}

viewModelScope i lifecycleScope — wbudowane CoroutineScope w Androidzie, które są anulowane przy odpowiednim zdarzeniu cyklu życia. Eliminuje to wycieki przez korutyny — najczęstszy scenariusz we współczesnym programowaniu Androida.

  • Nie używaj statycznych referencji do Context, Activity, View lub Fragment
  • Anuluj wszystkie subskrypcje RxJava w disposeBag / CompositeDisposable przy onDestroy
  • Używaj [weak self] / [unowned self] w zamknięciach iOS, aby zapobiegać retain cycles
  • Sprawdzaj Bitmap i duże obiekty — powinny być recyclowane lub wyzerowane

Narzędzia do profilowania pamięci

Memory Profiler in Android Studio — główne narzędzie do monitorowania sterty. Pokazuje live allocations, zrzuty sterty, liczbę obiektów według typów. Pozwala zapisać dump i przeanalizować go w MAT (Memory Analyzer Tool) w celu znalezienia podejrzanych obiektów.

Eclipse MAT — desktopowy analizator zrzutów sterty. Po załadowaniu pliku HPROF z Android Studio, MAT buduje drzewo dominatora, pokazuje retain size każdego obiektu i oferuje automatyczną analizę podejrzanych wycieków przez Leak Suspects Report.

Xcode Memory Graph — wizualny debugger retain cycles. Po kliknięciu przycisku Memory Graph Debugger Xcode zatrzymuje aplikację, buduje pełny graf obiektów w pamięci i podświetla retain cycles na czerwono.

NarzędziePlatformaCecha
LeakCanaryAndroidAutomatyczne wykrywanie wycieków po destroy
Memory ProfilerAndroid StudioHeap dump + live allocations
Eclipse MATAndroidDrzewo dominatora, Leak Suspects Report
Memory GraphiOS (Xcode)Wizualizator retain cycles

Według Google I/O 2023, aplikacje używające LeakCanary w kompilacjach debug redukują liczbę crashy związanych z pamięcią o 30-50% w ciągu pierwszych 2 miesięcy po wdrożeniu. Zaleca się dodawanie LeakCanary na etapie onboardingu projektu.

Często zadawane pytania

Czym różni się wyciek pamięci od rozdymania?

Wyciek — obiekty niedostępne dla kodu, ale nieusuwane przez GC z powodu aktywnych referencji. Rozdymanie — aplikacja przechowuje w pamięci obiekty, które logicznie są potrzebne, ale w nadmiarowej ilości (np. cache 50 MB przy działającej aplikacji ważącej 80 MB). Rozdymanie leczy się architektonicznie, wyciek — poprzez poprawne zarządzanie referencjami.

Jak LeakCanary wykrywa wycieki?

LeakCanary używa ObjectWatcher — po onDestroy() Activity tworzy WeakReference na Activity i uruchamia GC. Jeśli po 5 sekundach WeakReference nie jest wyczyszczony, LeakCanary wykonuje heap dump, analizuje najkrótszy łańcuch referencji od GC Root do obiektu i pokazuje dokładny stos wycieku z nazwą pliku i linią kodu.

Dlaczego Bitmap często powoduje OutOfMemoryError?

Bitmap zajmuje pamięć poza stertą Java w pamięci natywnej (native heap). Rozmiar jednego Bitmap = szerokość × wysokość × 4 bajty (ARGB_8888). Zdjęcie 12 MP (4000×3000) zajmuje 48 MB. Android nie zawsze może terminowo zwolnić pamięć natywną, co przy nagromadzeniu kilku Bitmap prowadzi do OOM nawet przy wystarczającej stercie Java.

Czym jest retain cycle w iOS?

Retain cycle — sytuacja w ARC, gdy dwa obiekty przechowują silne referencje wzajemnie, a licznik referencji nigdy nie osiąga zera. Typowy przykład: ViewController z silną referencją do closure, a closure przechwytuje self silnie. Rozwiązanie: użyj [weak self] lub [unowned self] w zamknięciach.

Jaki jest maksymalny rozmiar sterty w Androidzie?

Rozmiar sterty zależy od urządzenia i wersji Androida. Dla starych urządzeń (API 15-24) — 64-128 MB. Dla nowoczesnych (API 25+) — 256-512 MB. Dokładną wartość można uzyskać przez ActivityManager.getMemoryClass(). Dla dużych aplikacji (gry, edytory) istnieje largeHeap=true w manifeście, dające do 1 GB.

Podsumowanie

  • Wyciek pamięci — obiekt nieusuwany przez GC z powodu aktywnej referencji z zestawu korzeniowego; rozdymanie — nadmierne zużycie pamięci bez wyraźnych wycieków
  • Statyczne referencje do Activity, Context, View — numer jeden wśród przyczyn wycieków w Androidzie; rozwiązanie — WeakReference lub Application Context
  • Klasy anonimowe i lambdy niejawnie przechowują referencję do klasy zewnętrznej; nieanulowane callbacki — druga najczęstsza przyczyna
  • LeakCanary — standard automatycznego wykrywania wycieków w Androidzie; integracja zajmuje 5 minut i redukuje wskaźnik crashy o 30-50%
  • lifecycleScope i viewModelScope automatycznie anulują korutyny przy destroy, eliminując całą klasę wycieków
  • Retain cycles w iOS rozwiązuje się przez weak/unowned self w zamknięciach i delegatach
  • Profiluj pamięć przynajmniej raz na sprint — heap dump z MAT lub Memory Graph powinien stać się częścią code review

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ż