Introduction / Why This Matters
Memory profiling is a critical step in developing stable Android applications. Memory leaks lead to a gradual increase in RAM consumption, performance slowdowns, and in the worst case, crashes with an OutOfMemoryError. The built-in Memory Profiler in Android Studio allows you to visually track memory allocation in real-time, take heap dumps, and identify objects that shouldn't be in memory. This guide will walk you through the entire process, from launching the tool to interpreting the results.
Prerequisites / Preparation
Before you begin, ensure that:
- You have Android Studio version Hedgehog (2023.1.1) or newer installed. The tool is constantly improving.
- Your device or emulator is running Android 5.0 (API 21) or higher.
- Your app is built in a debug configuration (with debugging information enabled). Analysis of release builds is possible but more difficult.
- In your app's
build.gradle(module),debuggable trueis enabled for debug builds (this is typically the default). - Your device is connected via USB with USB debugging enabled, or an emulator is running.
Step 1: Launch Profiler and Connect Your Device
- Run your app on a device or emulator from Android Studio (click the
Runbutton). - Open the Profiler window:
View → Tool Windows → Profileror use the shortcutAlt+Shift+F10(Windows/Linux) /⌥⇧F10(macOS). - At the top of the Profiler window, you'll see a list of active processes. Select your app's process (usually named with the package name, e.g.,
com.example.myapp). - The CPU tab opens by default. Switch to the Memory tab (the icon with a blue graph and numbers).
On the graph, you'll see your app's real-time memory consumption:
- Java Heap: Memory managed by the JVM/ART (your code).
- Native Heap: Memory allocated via the NDK (C/C++ code).
- Graphics: Memory used for rendering buffers.
- Stack: Memory for stack frames.
- Code: Memory for compiled code.
- Others: Other memory.
Step 2: Start Recording and Perform a Scenario
- On the Memory tab's toolbar, click the
Record memory allocationsbutton (the icon with a red dot inside a circle). The recording status will change toRecording.... - Perform an action in your app that you suspect causes a leak. For example:
- Open and close a fragment/activity several times.
- Scroll a list (
RecyclerView) with many items. - Navigate to another screen and back.
- Important: Try to reproduce a scenario a user might perform repeatedly. A leak often manifests after several iterations.
- After performing the action, click the
Stop recordingbutton (the same button, now red). Object allocation recording will stop.
💡 Tip: Don't record for too long—this can generate a huge amount of data and slow down performance. 30-60 seconds of active actions is usually sufficient.
Step 3: Take a Heap Dump for Analysis
Recording allocations is useful for tracking object creation frequency, but to find leaks you need a Heap Dump—a complete snapshot of objects in the heap at a specific moment.
- Ensure you are on the Memory tab.
- Click the
Dump Java heapbutton (the icon with a heap of trash and a downward arrow). The profiler will request the system to create a dump. - Wait for it to finish. A new
Heap Dumptab will appear with the results.
Step 4: Analyze Results in the Analyzer
The Heap Dump tab automatically opens the analyzer. By default, you see the Classes table.
How to read the table:
Class— The object's class name.Instance Count— The number of instances of this class in the heap at the time of the dump.Size— The total memory occupied by all instances of this class (in KB/MB).Retained Size— The most important metric. This is the amount of memory that would be freed if all objects of this class (and those they reference) were deleted. A largeRetained Sizefor a class that shouldn't be in memory is a clear sign of a leak.
First action: Sort the table by the Retained Size column (descending). The classes holding the largest amounts of memory will be at the top.
What to look for:
- Classes from your app (e.g.,
MyActivity,MyAdapter,MyViewModel) that should not exist after a screen/fragment is closed. - Classes from libraries known for leaks (e.g., older versions of
androidx.lifecycle.ViewModelwith improper configuration). - A large number of
Bitmap,Context,View,Activity, orFragmentinstances.
Step 5: Investigate Paths to GC Roots
Found a suspicious class? Now you need to understand why it isn't being garbage collected. To do this, find the GC Root—the object that directly or through a chain of references holds your object in memory.
- In the
Classestable, find the suspicious class and select it (click the row). - On the right panel (or in the context menu), choose
Show Path to GC Roots. - A dialog will appear with options. Select
Show all paths(orShow soft referencesif you suspect caches). ClickOK. - The analyzer will build a reference tree from the GC Root (at the top) to your object (at the bottom).
How to interpret:
- The GC Root is typically a system object (e.g.,
android.app.ActivityThread,java.lang.Thread,android.view.ViewRootImpl) or a static field of your class (MyClass.sStaticField). - Look in the chain for an activity context (
Context), which might be stored in a static field or a long-lived singleton. This is a classic activity leak. - If the chain includes your
Singletonor anObjectfrom a cache—it means it isn't cleared when the screen is destroyed.
⚠️ Important: Not all paths to a GC Root indicate a leak. If the object is genuinely needed for app functionality (e.g., a service), its presence is justified. Look for objects that should have been destroyed (closed activities, fragments).
Verifying the Fix
After making code changes (e.g., clearing references in onDestroy(), using WeakReference, replacing static contexts with ApplicationContext), repeat steps 1-4.
Success criteria:
- On the Memory Profiler graph, after closing the problematic screen, memory (Java Heap) returns to a baseline level instead of growing.
- In a new Heap Dump, the
Instance Countof the suspicious class has significantly decreased or is zero. - In the analyzer, the
Retained Sizefor that class is now close to zero. - The path to the GC Root for that class no longer exists or leads to an object that legitimately lives for the entire app lifecycle.
Common Issues
1. Profiler doesn't show your app's process
- Cause: The app is built in a release configuration without the debuggable flag, or the device isn't authorized for debugging.
- Solution: Run a debug build. On the device, in
Settings → Developer options, ensure USB debugging is enabled and you have confirmed the computer's RSA key.
2. Heap Dump takes very long or fails with an error
- Cause: Too many objects in the heap (tens of thousands), or insufficient memory on your computer for analysis.
- Solution:
- Simplify the scenario; take the dump earlier.
- Close other heavy apps in the emulator/device.
- Increase the RAM allocated to the emulator (in AVD Manager).
- Use filters in the analyzer (the
Filterfield) to view only your packages (e.g.,com.example).
3. Can't find the problematic class—only system classes are listed
- Cause: The leak might be in native code (Native Heap) or in objects the Profiler cannot display correctly (e.g., due to transparent wrappers).
- Solution:
- Switch to the
Nativetab in Profiler and check native heap growth. - Use
adb shell dumpsys meminfo <package_name>in the terminal for a lower-level report. - If you use libraries with native code (e.g., OpenGL, FFmpeg), check their documentation for known leaks.
- Switch to the
4. Memory graph still grows after fixing the leak
- Cause: You have more than one leak, or the growth is due to legitimate caching (e.g., an
LruCache). - Solution:
- Compare several Heap Dumps taken at intervals. See which classes increase in count between dumps.
- Check if the growth is linear and predictable (e.g., adding items to an
ArrayListwithout clearing). This might be business logic, not a leak. - Analyze the
Allocation Tracker(theRecord allocationsbutton) to see where new objects are being created.
5. Path to GC Root goes through a system class I don't control
- Cause: Some system classes (e.g.,
InputMethodManager) historically have leaks via uncleared references. - Solution:
- Check if you are holding a reference to a
VieworContextin a static field or long-lived object yourself. - For known system leaks, there are established patterns: e.g., in an activity's
onDestroy(), callinputMethodManager.isActive = falseor clear focus fromEditText. - Search for the specific case (class name + "memory leak") in the Android documentation or on Stack Overflow.
- Check if you are holding a reference to a