Heap Dump: co to jest, analiza sterty i eliminacja wycieków pamięci

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

Heap Dump (zrzut sterty) — to migawka pamięci dynamicznej aplikacji zawierająca pełną informację o wszystkich żywych obiektach: ich klasach, rozmiarach, wzajemnych referencjach i dostępności od węzłów głównych (GC roots). Heap Dump to podstawowe narzędzie analizy wycieków pamięci i optymalizacji zużycia zasobów. Według Android Developers, analiza heap dumpów pozwala wykryć do 95% wycieków pamięci, w tym cykliczne referencje, zapomniane listenery i niezwolnione statyczne referencje.

Najważniejsze

  • Heap Dump — migawka całej pamięci dynamicznej aplikacji z informacją o każdym obiekcie i referencjach między nimi.
  • Android Studio Memory Profiler umożliwia przechwytywanie heap dumpu w czasie rzeczywistym dla aplikacji Java i Kotlin.
  • Xcode Instruments udostępnia narzędzie Allocations do tworzenia i analizy heap dumpów na iOS/macOS.
  • Shallow i retained size to kluczowe metryki: shallow to rozmiar samego obiektu, retained to rozmiar obiektu plus wszystkie utrzymywane przez niego obiekty.
  • Analiza heap dumpu obejmuje przeszukiwanie dominator tree, największych retained objects i najkrótszych ścieżek do GC roots.

Co to jest heap dump i do czego służy

Heap dump to pełny zrzut sterty (heap) maszyny wirtualnej — obszaru pamięci, w którym umieszczane są wszystkie dynamicznie tworzone obiekty. W Javie i Kotlin jest to Dalvik/ART na Androidzie, w Swift i Objective-C — sterta zarządzana przez ARC na iOS. Heap dump rejestruje każdy obiekt, jego klasę, rozmiar, pola, referencje do innych obiektów oraz flagi osiągalności od GC roots (zmienne stosowe, pola statyczne, referencje JNI).

Głównym celem heap dumpu jest wykrywanie wycieków pamięci. Wyciek powstaje, gdy aplikacja nadal utrzymuje referencje do obiektów, które nie są już potrzebne, uniemożliwiając ich zwolnienie przez garbage collector (lub zwolnienie przez ARC). Typowe przyczyny: listenery zdarzeń nieodpięte przy niszczeniu activity; singleton z referencjami do kontekstu; domknięcia (closures) przechwytujące self; statyczne kolekcje, do których dodawane są dane bez usuwania. Heap dump daje dokładny obraz: które obiekty są „żywe”, które są zbędne i kto dokładnie na nie wskazuje.

Według danych Google I/O, ponad 60% raportów crashów aplikacji Android jest związanych z OutOfMemoryError, a w 80% przypadków pierwotną przyczyną jest wyciek pamięci wykrywalny przez heap dump. W przypadku aplikacji iOS sytuacja jest podobna: wycieki spowodowane retain cycles są jedną z częstych przyczyn awarii wykrywanych przez instrument Allocations w Xcode.

Kiedy potrzebny jest heap dump

Heap dump należy wykonać przy następujących objawach: aplikacja zużywa pamięć liniowo przy powtarzających się czynnościach (przechodzenie tam i z powrotem między ekranami); po zakończeniu działania ekranu pamięć nie wraca do początkowego poziomu; występują OutOfMemoryError lub ostrzeżenia memory warning na iOS; aplikacja ulega zakończeniu z powodu przekroczenia limitu pamięci (EXC_RESOURCE_RESOURCE na iOS). Regularne zbieranie heap dumpów to część protokołu kultury inżynieryjnej w dużych projektach mobilnych, takich jak Instagram i Spotify.

Heap dump w Android Studio: pobieranie i analiza

Android Studio udostępnia Memory Profiler — wbudowane narzędzie do przechwytywania heap dumpu w czasie rzeczywistym. Dostępne przez View → Tool Windows → Profiler. Po uruchomieniu aplikacji wybierz sesję, przejdź do zakładki Memory i kliknij Dump Java Heap. Android Studio wstrzymuje aplikację, wykonuje zrzut sterty ART i ładuje wynik do analizy. Plik zrzutu ma format .hprof — standard HPROF, kompatybilny z większością analizatorów pamięci.

Po załadowaniu zrzutu Android Studio wyświetla tabelę obiektów z kolumnami: Allocations (liczba instancji), Native Size (pamięć poza stertą ART), Shallow Size (pamięć samego obiektu), Retained Size (pamięć obiektu z całym podgrafem). Filtrowanie po nazwie klasy, sortowanie po retained size i wyszukiwanie po pakietach pozwalają szybko znaleźć problematyczne obszary.

kotlin
// Typowy wyciek — listener nieodpięty w onDestroy
class MainActivity : AppCompatActivity() {
    private val sensorManager by lazy {
        getSystemService(SENSOR_SERVICE) as SensorManager
    }
    private val listener = MySensorListener()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sensorManager.registerListener(listener,
            sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
            SensorManager.SENSOR_DELAY_NORMAL)
    }

    override fun onDestroy() {
        super.onDestroy()
        // ❌ Pominięto sensorManager.unregisterListener(listener)
        // → Activity nie zostanie zebrane przez GC, heap dump pokaże wyciek
    }
}

Analiza dominator tree w Android Studio

Zakładka Dominator Tree pokazuje obiekty, które utrzymują największą ilość pamięci. Jeśli obiekt zostanie usunięty z dominator tree, cała pamięć, którą utrzymuje, stanie się dostępna do zwolnienia. To kluczowe narzędzie: zamiast przeglądać tysiące obiektów, skupiasz się na 10–20, które kontrolują 80–90% pamięci. Według Google, analiza dominator tree to najskuteczniejszy sposób znalezienia punktu wycieku, skracający czas analizy z godzin do minut.

Heap dump w Xcode Instruments: Allocations i Leaks

Xcode Instruments udostępnia dwa narzędzia do pracy z heap dumpem: Allocations — przechwytywanie zrzutu sterty z wykresem zużycia w czasie rzeczywistym; Leaks — automatyczne wyszukiwanie wycieków przez analizę retain cycles. Allocations wyświetla wszystkie obiekty na stercie, ich rozmiar, liczbę utworzeń (allocations) i zwolnień (deallocations). Różnica między liczbą utworzeń a zwolnień dla konkretnej klasy wskazuje potencjalny wyciek.

Przechwytywanie heap dumpu w Allocations wykonuje się przyciskiem Snapshot Memory — narzędzie wstrzymuje aplikację i wykonuje pełny zrzut. Następnie dostępne są standardowe widoki: lista obiektów według klas, drzewo wywołań (call tree) dla każdego obiektu i generator raportów. W przeciwieństwie do Android Studio, Xcode nie używa .hprof, lecz przechowuje dane we własnym formacie .trace, kompatybilnym z Instruments.

swift
// Typowy wyciek iOS — retain cycle przez domknięcie
class NetworkManager {
    var onComplete: ((Data) -> Void)?

    func startRequest() {
        // ❌ Domknięcie przechwytuje self — retain cycle
        onComplete = { data in
            self.process(data)
        }
    }
    func process(_ data: Data) {}
}

Leaks instrument automatycznie wykrywa retain cycles i wycieki przez analizę grafu referencji. Oznacza wyciekające obiekty fioletową ikoną i pokazuje ścieżkę do korzenia (GC root). Aby usunąć retain cycle, wystarczy dodać [weak self] lub [unowned self] w domknięciu. Regularne uruchamianie Leaks instrument to obowiązkowy etap CI w zespołach używających Swift do tworzenia aplikacji iOS.

swift
// Naprawa — słaba referencja do self
onComplete = { [weak self] data in
    guard let self else { return }
    self.process(data)
}

Shallow size, retained size i dominator tree

Do prawidłowej analizy heap dumpu należy rozumieć trzy kluczowe metryki. Shallow size — ilość pamięci zajmowanej bezpośrednio przez obiekt: jego pola, nagłówek (header) i wyrównanie. Dla typowego obiektu Java/Kotlin shallow size wynosi 16–40 bajtów. Retained size — shallow size obiektu plus sumaryczny shallow size wszystkich obiektów, które są dostępne tylko przez ten obiekt (czyli staną się śmieciem po jego usunięciu). To właśnie retained size pokazuje rzeczywisty wpływ obiektu na zużycie pamięci.

MetrykaOpisPrzykład
Shallow sizeRozmiar samego obiektu w bajtachBitmap (100×100) = 40 016 B
Retained sizeShallow size + wszystko, co utrzymujeActivity z View Tree = 2–5 MB
Deep sizeRetained size + zagnieżdżone obiekty z innych grafówScrollView z adapterem = 10–50 MB

Dominator tree — struktura, w której każdy obiekt wskazuje na swojego „dominatora” — obiekt kontrolujący jego dostępność. Jeśli dominator zostanie usunięty, wszystkie obiekty jego poddrzewa stają się śmieciem. Analiza dominator tree to najszybszy sposób znalezienia obiektu utrzymującego najwięcej pamięci. Według Eclipse MAT (Memory Analyzer Tool), 90% wycieków jest wykrywanych przez przeglądanie top-20 dominator tree w ciągu 5 minut.

Analiza wycieków pamięci przez heap dump

Proces analizy wycieku przez heap dump składa się z kilku kroków. Krok 1: wykonaj czynność, która powinna zwolnić pamięć (zamknij ekran, zakończ operację). Krok 2: wywołaj GC (System.gc() w Androidzie, wymuszony snapshot w Xcode) i wykonaj heap dump. Krok 3: znajdź obiekty, które powinny zostać zniszczone (np. instancja Activity po finish). Krok 4: dla podejrzanego obiektu wykonaj Path to GC Roots — łańcuch referencji utrzymujący obiekt przy życiu. Ostatnia referencja w łańcuchu to przyczyna wycieku.

Ścieżka do GC Roots (Path to GC Roots)

Funkcja Path to GC Roots jest dostępna w Android Studio Profiler, Eclipse MAT i Xcode Instruments. Pokazuje najkrótszy łańcuch referencji od GC root do problematycznego obiektu. Wykluczając słabe (weak) i miękkie (soft) referencje, otrzymujesz tylko silne (strong) — te, które rzeczywiście uniemożliwiają zwolnienie. Według statystyk Square Engineering, 70% wycieków w aplikacjach Android jest spowodowanych tylko dwoma wzorcami: statyczne referencje do Activity lub Context oraz zarejestrowane, ale nieodpięte listenery.

kotlin
// Przykład wycieku przez statyczną referencję
object AppCache {
    private val cache = mutableMapOf<String, Any>()

    fun storeActivityReference(activity: Activity) {
        cache["current_activity"] = activity // ❌ Wyciek!
    }
}

// Naprawa: słaba referencja
object AppCacheFixed {
    private val cache = mutableMapOf<String, WeakReference<Any>>()
}

Porównanie dwóch heap dumpów

Technika comparison mode to jedna z najskuteczniejszych metod wyszukiwania wycieków. Wykonaj heap dump przed i po powtarzającej się czynności (np. pięć przejść na ekran i z powrotem). Porównaj liczbę instancji kluczowych klas: jeśli liczba Activity wzrosła, mimo że wszystkie aktywności zostały zamknięte — to wyciek. Android Studio i Eclipse MAT obsługują automatyczne porównywanie zrzutów z wyróżnianiem różnic. Według Google, porównywanie zrzutów pozwala znajdować wycieki niewidoczne przy jednorazowej analizie dzięki kumulacji efektu.

Praktyczne zalecenia dotyczące zmniejszenia zużycia pamięci

Na podstawie analizy heap dumpów w rzeczywistych projektach wypracowano sprawdzone praktyki optymalizacji pamięci. Używaj WeakReference dla pamięci podręcznej, wywołań zwrotnych i referencji do kontekstu w długo żyjących obiektach. Odpiń listenery w onPause/onDestroy dla Androida i deinit dla iOS. Unikaj dużych statycznych kolekcji — jeśli są niezbędne, używaj LruCache z ograniczeniem rozmiaru. Optymalizuj Bitmapy: ładuj obrazy z odpowiednim inSampleSize, używaj Glide lub Picasso z pamięcią podręczną na dysku.

Profilowanie pamięci w trakcie tworzenia oprogramowania

Włącz regularne przechwytywanie heap dumpu w CI. Skonfiguruj zadanie, które uruchamia instrumentowane testy UI, wykonuje kluczowe scenariusze użytkownika i porównuje heap dump z baseline. Jeśli retained size wzrósł o więcej niż 5% od baseline — kompilacja jest oznaczana jako regresja. Takie podejście jest praktykowane w Airbnb, Uber i innych firmach o wysokich wymaganiach jakościowych. Według Uber Engineering, wdrożenie automatycznej analizy heap dumpu w CI zmniejszyło liczbę błędów związanych z pamięcią o 70% w ciągu kwartału.

groovy
// Przykład zadania Gradle do automatycznego heap dumpu w CI
task profileMemory(type: Exec) {
    commandLine 'adb', 'shell',
        'am start -n com.example/.MainActivity'
    // Oczekiwanie na załadowanie
    doLast {
        exec { commandLine 'adb', 'shell',
            'am broadcast -a com.example.DUMP_HEAP' }
    }
}

Często zadawane pytania

Jaka jest różnica między shallow size a retained size?

Shallow size — rozmiar samego obiektu (pola + nagłówek). Retained size — rozmiar obiektu plus wszystkich obiektów, które staną się śmieciem po jego usunięciu. Retained size to główny wskaźnik wpływu obiektu na zużycie pamięci.

Jak zrobić heap dump na fizycznym urządzeniu Android?

Przez Android Studio Profiler wybierz urządzenie i proces, kliknij Dump Java Heap. Alternatywnie — przez wiersz poleceń: adb shell am dumpheap PID /sdcard/dump.hprof, następnie adb pull.

Dlaczego heap dump może być ogromny (500 MB+)?

Heap dump obejmuje wszystkie żywe obiekty. Jeśli aplikacja używa pamięci podręcznej, bitmap lub przetwarza duże dane, zrzut może osiągnąć setki megabajtów. Filtruj według klas lub użyj Eclipse MAT do załadowania tylko indeksu.

Czy można analizować heap dump bez Android Studio?

Tak, użyj Eclipse MAT (Memory Analyzer Tool) — darmowego narzędzia do analizy .hprof. Obsługuje dominator tree, path to GC roots, porównywanie zrzutów i automatyczne wyszukiwanie wycieków przez Leak Suspects Report.

Czy heap dump zmniejsza wydajność aplikacji?

Sam zrzut — tak, ponieważ tworzenie zrzutu wstrzymuje wszystkie wątki (stop-the-world). Bez zrzutu — nie. Wykonuj zrzut w kontrolowanych warunkach (środowisko testowe, CI), nie na produkcji.

Podsumowanie

  • Heap Dump — pełna migawka sterty aplikacji z informacją o każdym obiekcie i powiązaniach między nimi.
  • Android Studio Memory Profiler i Xcode Instruments Allocations — podstawowe narzędzia do przechwytywania zrzutów.
  • Shallow size — rozmiar samego obiektu; retained size — rozmiar obiektu z całym podgrafem zależności.
  • Dominator tree — drzewo dominatorów pokazujące obiekty kontrolujące największą pamięć.
  • Path to GC Roots — łańcuch silnych referencji utrzymujący obiekt przed zwolnieniem.
  • Porównanie dwóch heap dumpów (przed/po działaniu) — najpewniejsza metoda wykrywania wycieków.
  • Automatyzacja przechwytywania i analizy heap dumpów w CI zapobiega regresjom pamięci na etapie tworzenia oprogramowania.

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ż