Heap Dump (dump на купчината) — моментна снимка на динамичната памет на приложението, съдържаща пълна информация за всички живи обекти: техните класове, размери, взаимни референции и достъпност от кореновите възли (GC roots). Heap Dump е основният инструмент за анализ на течове на памет и оптимизация на потреблението на ресурси. Според Android Developers, анализът на heap dumps позволява откриване на до 95% от течовете на памет, включително циклични референции, забравени listeners и неосвободени статични референции.
Основни точки
Heap dump представлява пълен dump на купчината (heap) на виртуалната машина — областта от паметта, където се поставят всички динамично създавани обекти. В Java и Kotlin това е Dalvik/ART на Android, в Swift и Objective-C — управляваната от ARC купчина на iOS. Heap dump записва всеки обект, неговия клас, размер, полета, референции към други обекти и флагове за достижимост от GC roots (стекови променливи, статични полета, JNI референции).
Основната цел на heap dump е откриване на течове на памет. Теч възниква, когато приложението продължава да задържа референции към обекти, които вече не са необходими, предотвратявайки тяхното събиране от garbage collector (или освобождаване чрез ARC). Типични причини: слушатели на събития, които не са отписани при унищожаване на activity; сингълтони с референции към контекст; затваряния (closures), които улавят self; статични колекции, към които се добавят данни без изтриване. Heap dump дава точна картина: кои обекти са „живи", кои са излишни и кой точно реферира към тях.
Според данни на Google I/O, над 60% от отчетите за сривове на Android приложения са свързани с OutOfMemoryError, а в 80% от случаите основната причина е теч на памет, откриваем чрез heap dump. За iOS приложения ситуацията е подобна: течовете, причинени от retain cycles, са една от честите причини за сривове, откривани чрез инструмента Allocations в Xcode.
Heap dump трябва да се извърши при следните симптоми: приложението консумира памет линейно при повтарящи се действия (навигиране напред-назад между екрани); след приключване на екрана паметта не се връща към първоначалното ниво; появяват се OutOfMemoryError или предупреждения memory warning на iOS; приложението се прекратява поради превишаване на лимита на паметта (EXC_RESOURCE_RESOURCE на iOS). Редовното събиране на heap dumps е част от протокола на инженерната култура в големи мобилни проекти като Instagram и Spotify.
Android Studio предоставя Memory Profiler — вграден инструмент за заснемане на heap dump в реално време. Достъпен чрез View → Tool Windows → Profiler. След стартиране на приложението изберете сесия, отидете на раздела Memory и щракнете върху Dump Java Heap. Android Studio спира приложението, извършва dump на ART купчината и зарежда резултата за анализ. Файлът на dump-а има формат .hprof — стандарт HPROF, съвместим с повечето анализатори на памет.
След зареждане на dump-а Android Studio показва таблица с обекти с колони: Allocations (брой инстанции), Native Size (памет извън ART купчината), Shallow Size (памет на самия обект), Retained Size (памет на обекта с целия подграф). Филтриране по име на клас, сортиране по retained size и търсене по пакети позволяват бързо откриване на проблемни области.
// Типичен теч — слушател, неотписан в 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()
// ❌ Пропуснат sensorManager.unregisterListener(listener)
// → Activity няма да бъде събрана от GC, heap dump ще покаже теча
}
}
Разделът Dominator Tree показва обектите, които задържат най-голямо количество памет. Ако обект бъде изтрит от dominator tree, цялата памет, която той задържа, става достъпна за събиране. Това е ключовият инструмент: вместо да преглеждате хиляди обекти, се фокусирате върху 10-20, които контролират 80-90% от паметта. Според Google, анализът на dominator tree е най-ефективният начин за намиране на точка на теч, съкращаващ времето за анализ от часове до минути.
Xcode Instruments предоставя два инструмента за работа с heap dump: Allocations — заснемане на dump на купчината с графика на потреблението в реално време; Leaks — автоматично търсене на течове чрез анализ на retain cycles. Allocations показва всички обекти в купчината, техния размер, брой създавания (allocations) и освобождавания (deallocations). Разликата между броя създавания и освобождавания за конкретен клас показва потенциален теч.
Заснемането на heap dump в Allocations се извършва с бутона Snapshot Memory — инструментът спира приложението и прави пълен dump. След това са налични стандартните изгледи: списък на обектите по класове, дърво на извикванията (call tree) за всеки обект и генератор на отчети. За разлика от Android Studio, Xcode не използва .hprof, а съхранява данни в собствен формат .trace, съвместим с Instruments.
// Типичен iOS теч — retain cycle чрез затваряне
class NetworkManager {
var onComplete: ((Data) -> Void)?
func startRequest() {
// ❌ Затварянето улавя self — retain cycle
onComplete = { data in
self.process(data)
}
}
func process(_ data: Data) {}
}
Leaks инструментът автоматично открива retain cycles и течове чрез анализ на референтната графа. Той маркира обектите с теч с лилава икона и показва пътя до корена (GC root). За отстраняване на retain cycle е достатъчно да добавите [weak self] или [unowned self] в затварянето. Редовното стартиране на Leaks инструмента е задължителен етап от CI в екипите, използващи Swift за iOS разработка.
// Поправка — слаба референция към self
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
За коректен анализ на heap dump е необходимо да разберете три ключови метрики. Shallow size — обемът памет, зает директно от обекта: неговите полета, заглавна част (header) и подравняване. За типичен Java/Kotlin обект shallow size е 16-40 байта. Retained size — shallow size на обекта плюс общият shallow size на всички обекти, които са достъпни само чрез този обект (т.е. ще станат боклук при неговото изтриване). Именно retained size показва реалното влияние на обекта върху потреблението на памет.
| Метрика | Описание | Пример |
|---|---|---|
| Shallow size | Размер на самия обект в байтове | Bitmap (100×100) = 40 016 B |
| Retained size | Shallow size + всичко, което задържа | Activity с View Tree = 2-5 MB |
| Deep size | Retained size + вложени обекти от други графи | ScrollView с адаптер = 10-50 MB |
Dominator tree — структура, в която всеки обект реферира към своя „доминатор" — обекта, който контролира неговата достъпност. Ако доминаторът бъде изтрит, всички обекти на неговото поддърво стават боклук. Анализът на dominator tree е най-бързият начин за намиране на обекта, който задържа най-много памет. Според Eclipse MAT (Memory Analyzer Tool), 90% от течовете се откриват чрез преглед на top-20 dominator tree за 5 минути.
Процесът на анализ на теч чрез heap dump се състои от няколко стъпки. Стъпка 1: извършете действието, което трябва да освободи паметта (затворете екрана, завършете операцията). Стъпка 2: извикайте GC (System.gc() в Android, принудителен snapshot в Xcode) и направете heap dump. Стъпка 3: намерете обекти, които е трябвало да бъдат унищожени (напр. инстанция на Activity след finish). Стъпка 4: за подозрителния обект изпълнете Path to GC Roots — веригата от референции, която държи обекта жив. Последната референция във веригата е причината за теча.
Функцията Path to GC Roots е достъпна в Android Studio Profiler, Eclipse MAT и Xcode Instruments. Тя показва най-кратката верига от референции от GC root до проблемния обект. Чрез изключване на слабите (weak) и меките (soft) референции получавате само силните (strong) — тези, които действително предотвратяват събирането. Според статистика на Square Engineering, 70% от течовете в Android приложения са причинени от само два модела: статични референции към Activity или Context и регистрирани, но неотписани слушатели.
// Пример за теч чрез статична референция
object AppCache {
private val cache = mutableMapOf<String, Any>()
fun storeActivityReference(activity: Activity) {
cache["current_activity"] = activity // ❌ Теч!
}
}
// Поправка: слаба референция
object AppCacheFixed {
private val cache = mutableMapOf<String, WeakReference<Any>>()
}
Техниката comparison mode — един от най-ефективните методи за намиране на течове. Направете heap dump преди и след повтарящо се действие (напр. пет навигации до екран и обратно). Сравнете броя инстанции на ключовите класове: ако броят на Activity се е увеличил, въпреки че всички активности са затворени — това е теч. Android Studio и Eclipse MAT поддържат автоматично сравнение на dump-ове с подчертаване на разликите. Според Google, сравнението на dump-ове позволява откриване на течове, невидими при еднократен анализ, благодарение на натрупването на ефекта.
Въз основа на анализ на heap dump-ове в реални проекти са разработени доказани практики за оптимизация на паметта. Използвайте WeakReference за кешове, обратни извиквания и референции към контекст в дългоживеещи обекти. Отписвайте слушателите в onPause/onDestroy за Android и deinit за iOS. Избягвайте големи статични колекции — ако са необходими, използвайте LruCache с ограничение на размера. Оптимизирайте Bitmap-ите: зареждайте изображения с правилен inSampleSize, използвайте Glide или Picasso с дисков кеш.
Включете редовно заснемане на heap dump в CI. Конфигурирайте задача, която изпълнява инструментирани UI тестове, извършва ключови потребителски сценарии и сравнява heap dump-а с baseline. Ако retained size се е увеличил с повече от 5% спрямо baseline, сборката се маркира като регресия. Този подход се практикува в Airbnb, Uber и други компании с високи изисквания за качество. Според Uber Engineering, внедряването на автоматичен анализ на heap dump в CI намали броя на грешките, свързани с паметта, със 70% за едно тримесечие.
// Пример за Gradle задача за автоматичен heap dump в CI
task profileMemory(type: Exec) {
commandLine 'adb', 'shell',
'am start -n com.example/.MainActivity'
// Изчакване на зареждане
doLast {
exec { commandLine 'adb', 'shell',
'am broadcast -a com.example.DUMP_HEAP' }
}
}
Често задавани въпроси
Shallow size — размерът на самия обект (полета + заглавна част). Retained size — размерът на обекта плюс всички обекти, които ще станат боклук при неговото изтриване. Retained size е основният индикатор за влиянието на обекта върху потреблението на памет.
Чрез Android Studio Profiler изберете устройството и процеса, щракнете върху Dump Java Heap. Алтернативно — чрез командния ред: adb shell am dumpheap PID /sdcard/dump.hprof, след това adb pull.
Heap dump включва всички живи обекти. Ако приложението използва кешове, Bitmap-и или обработва големи данни, dump-ът може да достигне стотици мегабайта. Филтрирайте по класове или използвайте Eclipse MAT за зареждане само на индекса.
Да, използвайте Eclipse MAT (Memory Analyzer Tool) — безплатен инструмент за анализ на .hprof. Поддържа dominator tree, path to GC roots, сравнение на dump-ове и автоматично търсене на течове чрез Leak Suspects Report.
Самият dump — да, защото събирането на dump спира всички нишки (stop-the-world). Без dump — не. Правете dump в контролирани условия (тестова среда, CI), не на продукция.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също