Native memory refers to allocations made in C, C++, or Rust code using functions like malloc, free, or operators like new and delete. Unlike Java, native memory is not automatically garbage-collected; you are responsible for managing the lifecycle of every allocation.
Throughout this guide, we will use the MemoryLab sample application to demonstrate memory concepts. Before starting the exercises, ensure your device is connected with adb root and build the app:
# From the root of your AOSP checkout source build/envsetup.sh lunch <your_target_device>-userdebug adb root adb wait-for-device m MemoryLab adb install -r $OUT/system/app/MemoryLab/MemoryLab.apk
heapprofd is the platform-wide native heap profiler for Android. It uses a sampling-based approach to record allocations and deallocations with minimal overhead.
The easiest way to capture a native heap profile is using the heap_profile script provided by Perfetto.
Download the script from the Perfetto repository:
curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile chmod +x heap_profile
Ensure your device is connected via ADB and run the tool, specifying the target process:
./heap_profile -n <package_name_or_process_name>
Perform the user journey in your application.
Stop the profiler (Ctrl+C). The script will automatically pull the profile, start a local pprof server, and open your web browser to view the flamegraph.
You can also trigger a “dump” of the current heap state while a Perfetto trace is running:
adb shell killall -USR1 heapprofd
This creates a snapshot (diamond icon) in the Perfetto UI. See configs/heapprofd.pbtxt for an example configuration.
The output of heapprofd is a set of .pb.gz files. If you used the heap_profile script, these files are automatically pulled to your host machine's temporary directory (e.g., /tmp/heap_profile-XXXXXX on Linux or macOS), and a convenient symlink is created at /tmp/heap_profile-latest.
Note: If you recorded the heap profile as part of a full trace using the Perfetto UI or CLI, the heap dumps are embedded within the .pftrace file. You can extract them into .pb.gz format using the Perfetto traceconv tool.
See also: Recording memory profiles with Perfetto
You can manually analyze these files using pprof, a tool for visualization and analysis of profiling data.
Upload your profile to a pprof viewer such as Google pprof, available on GitHub.
Note for Googlers: You may use the internal pprof.corp.google.com tool, which is a server-hosted version of Google pprof.
If you captured the native heap profile within a full Perfetto trace (using heapprofd), you can query the raw allocations. This is useful for counting objects or summarizing bytes:
SELECT upid, count(id) AS allocation_count, sum(size) AS total_bytes FROM heap_profile_allocation GROUP BY upid ORDER BY total_bytes DESC;
You can also view the total count or bytes allocated during the profile, even if they were subsequently freed. This is useful for finding “allocation churn.”
If you see “unknown” frames in your flamegraph, you need to symbolize the profile. This requires providing the unstripped versions of your native libraries (.so files with debug symbols).
Where to find symbols in AOSP: Symbols are generated during the build process and stored at: out/target/product/<device_name>/symbols/
Use the --sym-dir flag with the heap_profile tool:
./heap_profile -n <process> --sym-dir $ANDROID_PRODUCT_OUT/symbols
On modern Android devices, a significant portion of memory is often consumed by graphics buffers, known as DMA-BUFs. These are used for UI layers, camera frames, and video buffers.
Because DMA-BUFs are shared between processes (e.g., between your app and the surfaceflinger or camera service), they can be hard to track.
Next: App Code is Memory