Memory Profiling Mobile Apps
App works normally first 5 minutes, then starts stuttering, after 15 minutes — crashes. NSLog: Received memory warning. Classic gradual memory leak story: something holds objects that should free, RSS grows, system kills process. Finding what holds them — profiler's job.
Tools and What They Show
Xcode Instruments — Leaks and Allocations
Allocations — shows all live objects in memory each moment. Most useful view — "Generation Analysis": Mark Generation before action, execute action several times, see what accumulates between generations and doesn't free.
Scenario: open DetailViewController, close, repeat 10 times. In Allocations — each time PhotoProcessingService object added, not freed. Go to Leaks tool — builds object graph in memory, finds cyclic references. See: DetailViewController → PhotoProcessingService → DetailViewController via delegate without weak. One weak var delegate — leak eliminated.
Heap Shot in Allocations — heap snapshot at moment. Compare two snapshots before and after operation. Difference = objects stayed in memory. More accurate than Leaks for finding logical leaks (objects in memory without cyclic refs but not needed — old items in unlimited cache).
Android Studio Memory Profiler
Shows heap in real-time: Java heap, Native heap, Stack, Graphics. Capture heap dump button — snapshot of all live objects with path to GC root. See per-class instance count and summed retained size.
Typical finding: Bitmap objects in Native heap. Pre-Android 8 bitmaps stored in Java heap, collected by GC. Android 8+ — in native heap, Memory Profiler shows separately. If native heap grows — find Bitmap without recycle() or Glide/Picasso with disabled LRU cache.
Allocation tracking — record all allocations during period. Heavy mode, enable only for specific scenario. Shows call stack per allocation — see who creates objects in hot path loop.
LeakCanary — Mandatory Android Tool
LeakCanary auto-detects Activity, Fragment, ViewModel, LiveData leaks etc. Just add dependency to debug flavor — works in background, shows notification with full stack on leak detection. iOS analog — LifetimeTracker or FBRetainCycleDetector.
// build.gradle (debug)
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
No additional setup needed. Works.
Most Common Leak Patterns
Static Context references (Android). companion object { val instance = MyHelper(context) } — if context is Activity not applicationContext — Activity leak on rotation.
Closure in Swift without [weak self]. networkService.fetch { data in self.update(data) } — if closure saved (e.g., in pending callbacks array) — strong self ref prevents deallocation.
NotificationCenter subscriptions without unsubscribe. iOS pre-Swift 5.3 addObserver without removeObserver — classic leak. With NotificationCenter.default.publisher(for:) via Combine and storing in cancellables — auto-solved.
Handler in Android. Handler(Looper.getMainLooper()) with postDelayed holds Activity via implicit inner class ref. Use WeakReference<Activity> or switch to lifecycleScope.launch { delay(ms) ... }.
Case: 200 MB Leak Over Session
Android app with maps: memory grew 80 to 280 MB over 20-minute navigation. Memory Profiler showed — MapTile objects (map raster tiles) not freed after leaving map screen. MapView didn't call onDestroy because Fragment with map in backstack without destroyView. Replace with FragmentTransaction.remove() instead of addToBackStack() + manual mapView.onDestroy() — leak eliminated.
Memory Profiling Stages
- Baseline: measure memory consumption at rest and under load
- Stress test: repeat key scenarios 20–50 times, watch memory growth trend
- Heap dump analysis: find objects with unexpectedly high retained size
- Leak confirmation: reproduce leak with LeakCanary / Instruments Leaks
- Fix & verify: fix, verify RSS stabilized
Timelines
Memory profiling and analysis — 2–3 days. Fixing found leaks — 1 day to 2 weeks depending on problem depth.







