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 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.
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.
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.
// 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
}
}
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.
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.
// 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.
// Naprawa — słaba referencja do self
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
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.
| Metryka | Opis | Przykład |
|---|---|---|
| Shallow size | Rozmiar samego obiektu w bajtach | Bitmap (100×100) = 40 016 B |
| Retained size | Shallow size + wszystko, co utrzymuje | Activity z View Tree = 2–5 MB |
| Deep size | Retained size + zagnieżdżone obiekty z innych grafów | ScrollView 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.
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.
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.
// 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>>()
}
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.
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.
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.
// 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
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.
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.
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.
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.
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
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.
Przeczytaj również