Android profiler : memory

Android official website: Use the memory analyzer to view the memory usage of your application

Use Memory Analyzer to view your app's memory usage

Memory Profiler is a component in Android Profiler that helps you identify memory leaks and memory thrashing that can cause your app to stutter, freeze, or even crash. It displays a real-time graph of your application's memory usage and lets you capture heap dumps, force garbage collection, and track memory allocations.

To open the Memory Analyzer, follow these steps:

  • ①Click View > Tool Windows > Profiler (you can also click the Profile icon in the toolbar).
  • ②Select the device and application process to be analyzed from the Android Profiler toolbar. If you have connected a device via USB but it is not listed, make sure you have USB debugging enabled.
  • ③Click anywhere on the MEMORY timeline to open the memory analyzer.
    Alternatively, you can use dumpsys from the command line to inspect your application memory and view GC events in logcat.

Why you should analyze your app memory

Android provides a managed memory environment - when it determines that your app no ​​longer uses certain objects, the garbage collector releases unused memory back to the heap. While the way Android finds unused memory continues to improve, as with all versions of Android, the system must briefly pause your code at some point. Most of the time, these pauses are imperceptible. However, if your app allocates memory faster than the system can reclaim it, your app may experience delays while the collector frees enough memory to satisfy your allocation needs. This delay can cause your app to skip frames, making it noticeably slower.

Even if your app doesn't show any slowdown, if there is a memory leak, the app may still retain memory when it goes to the background. This behavior causes the system to force unnecessary garbage collection events, thus slowing down memory performance for the rest of the system. Eventually, the system will be forced to kill your app process to reclaim memory. Then, when the user returns to your app, it must be fully restarted.

To help prevent these problems, you should use a memory profiler to do the following:

  • Look on the timeline for suboptimal memory allocation patterns that may cause performance issues.
  • Dump the Java heap to see what objects are occupying memory at any given time. Taking multiple heap dumps over an extended period of time can help identify memory leaks.
  • Record memory allocations during user interaction under normal and extreme conditions to accurately identify whether your code is allocating too many objects in a short period of time, or if the allocated objects are leaking.
    For programming practices that can reduce application memory usage, read Managing Application Memory.

Memory Analyzer Overview

When you first open the Memory Analyzer, you'll see a detailed timeline of your app's memory usage, and use various tools to force garbage collection, capture heap dumps, and log memory allocations.

Insert image description here

Figure 1. Memory analyzer

As shown in Figure 1, the default view of the Memory Analyzer includes the following:

  • ①Button used to force garbage collection events.
  • ②Button for capturing heap dump.
    Note: A button to record memory allocations appears to the right of the heap dump button only when connected to a device running Android 7.1 (API level 25) or lower.
  • ③ Drop-down menu for specifying how often the performance analyzer captures memory allocations. Choosing the appropriate options can help you improve application performance when profiling.
  • ④Button for zooming the timeline.
  • ⑤Button for jumping to real-time memory data.
  • ⑥Event timeline, showing activity status, user input events and screen rotation events.
  • ⑦Memory usage timeline, it will display the following:
  • A stacked chart showing how much memory each memory category is currently using, as shown by the y-axis on the left and the colored keys at the top.
  • A dashed line indicating the number of allocated objects, as shown on the y-axis on the right.
  • An icon for each garbage collection event.

However, if you're using a device running Android 7.1 or lower, not all analytics data is visible by default. If you see a message that says "Advanced profiling is unavailable for the selected process," you need to enable advanced profiling to see the following:

  • event timeline
  • Number of objects allocated
  • Garbage Collection Events
    On Android 8.0 and higher, advanced profiling is always enabled for debuggable apps.

Memory calculation method

The number you see at the top of the Memory Analyzer (Figure 2) is based on all private memory pages submitted by your app (this data is provided by the Android system based on its records). This count does not include pages shared with the system or other apps.

Insert image description here

Figure 2. Memory count legend at top of Memory Analyzer

The categories in the memory count are as follows:

  • Java: Memory allocated for objects from Java or Kotlin code.

  • Native: Memory for objects allocated from C or C++ code.

Even if you don't use C++ in your app, you may see some native memory used here because even if you write code in Java or Kotlin, the Android framework still uses native memory to handle various tasks on your behalf, such as processing Image resources and other graphics.

  • Graphics: The memory used by the graphics buffer queue to display pixels to the screen (including GL surfaces, GL textures, etc.). (Note that this is memory shared with the CPU, not GPU-specific memory.)

  • Stack: The memory used by the native stack and Java stack in your application. This usually has to do with how many threads your app is running.

  • Code: The memory your app uses to process code and resources such as dex bytecode, optimized or compiled dex code, .so libraries, and fonts.

  • Others: Your application uses memory that the system is unsure of how to classify.

  • Allocated: The number of Java/Kotlin objects allocated by your app. This number does not take into account objects allocated in C or C++.

If connecting to a device running Android 7.1 and lower, this allocation count only starts when Memory Analyzer connects to your running app. Therefore, any objects allocated before you start profiling will not be counted. However, Android 8.0 and higher comes with an on-device profiling tool that tracks all allocations, so on Android 8.0 and higher, this number always represents the total number of Java objects pending in your app.

The new memory analyzer logs your memory differently compared to the memory counts in the previous Android Monitor tool, so your memory usage may look higher now. The Memory Analyzer monitors a few additional categories, which adds to the total memory usage, but if you are only concerned about Java heap memory, the numbers for the "Java" entry should be similar to those in the previous tool. However, the Java numbers may not be exactly the same as what you see in Android Monitor because the new numbers take into account all the physical memory pages allocated to the app's Java heap since it was derived from Zygote. Therefore, it accurately reflects how much physical memory your application actually uses.

Note: When using devices running Android 8.0 (API level 26) and higher, Memory Profiler also shows some false positives of native memory usage in your app, when the memory is actually used by the profiling tool. For approximately 100,000 objects, this increases the reported memory usage by up to 10MB. In a future version of the IDE, these numbers will be filtered out of your data.

View memory allocation

The memory allocation chart shows you how each Java object and JNI reference in memory is allocated. Specifically, the Memory Analyzer can show you the following information about object allocation:

  • What types of objects are allocated and how much space they use.
  • Stack trace for each allocation, including in which thread.
  • When the object is deallocated (only when using a device running Android 8.0 or higher).
    To record Java and Kotlin allocations, select Record Java / Kotlin allocations and then choose Record. If the device is running Android 8 or higher, the Memory Analyzer interface will transition to a separate screen showing recording in progress. You can interact with the mini-timeline above the recording (e.g. change the selection). To finish recording, select the Stop icon.

Insert image description here

Visualize Java allocations in Memory Profiler

On Android 7.1 and lower, Memory Analyzer uses the legacy allocation recording feature, which displays the recording on the timeline until you tap Stop.

After selecting an area of ​​the timeline (or after completing a recording session using a device running Android 7.1 or earlier), a list of allocated objects appears, grouped by class name and sorted by their heap count.

Note: On Android 7.1 and lower, you can record up to 65535 allocations. If your recording session exceeds this limit, only the latest 65535 allocations are saved in the recording. (On Android 8.0 and above, there is no real limit.)
To check your allocation records, follow these steps:

  • ①Browse the list to find objects with abnormally large heap counts and possible leaks. To help find known classes, click the Class Name column heading to sort alphabetically. Then, click on a class name. The Instance View pane will appear on the right, displaying each instance of the class, as shown in Figure 3.
    Alternatively, you can quickly find objects by clicking the Filter icon or pressing Ctrl+F (Command+F on Mac) and entering the class or package name in the search field. You can also search by method name if you select Arrange by callstack from the drop-down menu. To use regular expressions, check the box next to Regex. If your search query is case-sensitive, check the box next to Match case.
  • ②In the Instance View pane, click an instance. The Call Stack tab will appear below, showing where the instance is assigned and in which thread.
  • ③In the Call Stack tab, right-click on any line and select Jump to Source to open the code in the editor.

Insert image description here

Figure 3. Detailed information about each assigned object is displayed in the Instance View on the right

You can use the two menus above the allocated objects list to select which heap to examine and how to organize the data.

From the menu on the left, select the heap to inspect:

  • default heap: When the system does not specify a heap.

  • image heap: System boot image, containing classes preloaded during startup. The allocation here ensures that it will never move or disappear.

  • zygote heap: Copy-on-write heap, in which the application process is derived from the Android system.

  • app heap: The main heap where your app allocates memory.

  • JNI heap: Shows the heap location where Java Native Interface (JNI) references are allocated and released.
    From the menu on the right, choose how to schedule assignments:

  • Arrange by class: Groups all assignments based on class name. It's the default value.

  • Arrange by package: Groups all assignments based on package name.

  • Arrange by callstack: Group all allocations into their corresponding call stacks.

Improve application performance when profiling

To improve application performance when profiling, the Memory Profiler periodically samples memory allocations by default. You can change this behavior using the Allocation Tracking drop-down menu when testing on devices running API level 26 or higher. The available options are as follows:

  • Full: Captures all object allocations in memory. This is the default behavior in Android Studio 3.2 and lower. If you have an application that allocates a large number of objects, you may observe a significant slowdown in your application when profiling.
  • Sampled: Periodically samples the allocation of objects in memory. This is the default option and has less impact on application performance when profiling. Applications that allocate large numbers of objects in a short period of time may still exhibit significant slowdowns.
  • Off: Stops tracking the application's memory allocation.

Note: By default, Android Studio stops tracking real-time allocation when performing CPU logging, and turns it back on after CPU logging is complete. You can change this behavior in the CPU Logging Configuration dialog box.

View global JNI references

Java Native Interface (JNI) is a framework that allows Java code and native code to call each other.

JNI references are managed by native code, so Java objects used by native code may remain active for too long. If a JNI reference is dropped without first explicitly removing it, some objects on the Java heap may become inaccessible. Additionally, the global JNI reference limit may be reached.

To troubleshoot such issues, use the JNI heap view in the Memory Analyzer to browse all global JNI references and filter them by Java type and native call stack. This information allows you to know when and where global JNI references are created and deleted.

While your app is running, select the part of the timeline you want to examine and select JNI heap from the drop-down menu above the class list. You can then inspect the object in the heap as usual, or double-click the object in the Allocation Call Stack tab to see where in the code the JNI references are allocated and released, as shown in Figure 4.

Insert image description here

Figure 4. View global JNI references

To examine memory allocations for your app's JNI code, you must deploy your app to a device running Android 8.0 or higher.

To learn more about JNI, see JNI Tips.

Native memory analyzer

Android Studio Memory Analyzer includes a native memory analyzer for apps deployed to physical devices running Android 10; support for Android 11 devices is now available in Android Studio 4.2 preview.

The Native Memory Analyzer tracks the allocation/deallocation of objects represented in native code over a specific time period and provides the following information:

  • Allocations: Number of objects allocated via malloc() or new operator during the selected time period.
  • Deallocations: The number of objects deallocated via the free() or delete operator during the selected time period.
  • Allocations Size: The total size in bytes of all allocations during the selected time period.
  • Deallocations Size: Total size (in bytes) of all freed memory during the selected time period.
  • Total Count: The value in the Allocations column minus the value in the Deallocations column.
  • Remaining Size: The value in the Allocations Size column minus the value in the Deallocations Size column.

Insert image description here

Native memory analyzer

To record native allocations on devices running Android 10 and higher, select Record native allocations, and then select Record. Recording continues until you click the Stop icon, after which the Memory Analyzer interface transitions to a separate screen showing native recording.

Insert image description here

"Record native allocations" button

In Android 9 and below, the Record native allocations option is not available.

By default, the native memory profiler uses a sample size of 32 bytes: every time 32 bytes of memory are allocated, the system takes a snapshot of the memory. The smaller the sample size, the more frequent the snapshots, resulting in more accurate memory usage data. The larger the sample size, the less accurate the data, but it takes up fewer system resources and therefore provides better performance when recording.

To change the sample size for the native memory profiler:

  • ①Select Run > Edit Configurations.
  • ②Select your application module in the left pane.
  • ③Click the Profiling tab, and then enter the sampling size in the field marked Native memory sampling interval (bytes).
  • ④Build and run your application again.

Note: The memory data provided by the native memory profiler is different from the data provided by the Java heap memory profiler. The native memory profiler only tracks allocations through the C/C++ allocator (including native JNI objects) and does not profile objects on the Java heap.
The native memory profiler is built on heapprofd from the Perfetto stack of performance profiling tools. For details on the internals of the native memory profiler, see the heapprofd documentation.

Note: Starting with the original Android Studio 4.1 version, the native memory profiler is disabled during app startup. This option will be enabled in an upcoming release.
The workaround is to use the Perfetto standalone command line profiler to capture the startup configuration file.

Capture heap dump

Heap dumps show which objects in your app were using memory at the time you captured the heap dump. Especially after long user sessions, heap dumps can help identify memory leaks by showing objects that you thought should no longer be in memory but are still there.

After capturing the heap dump, you can view the following information:

  • What types of objects your app allocates, and how many of each type there are.
  • How much memory each object is currently using.
  • Where in the code is a reference to each object maintained.
  • The call stack to which the object is allocated. (Currently, for Android 7.1 and lower, heap dumps of the call stack are only displayed if the heap dump is captured during recording allocation.)
    To capture a heap dump, click Capture heap dump and select Record. During a heap dump, the amount of Java memory may temporarily increase. This is normal because the heap dump occurs in the same process as your app and requires some memory to collect the data.

After the profiler captures the heap dump, the Memory Profiler interface transitions to a separate screen showing the heap dump.

Insert image description here

Figure 5. View the heap dump.

If you need to be more precise about when a dump was created, you can create heap dumps at critical points in your application code by calling dumpHprofData().

In the class list, you can view the following information:

  • Allocations: The number of allocations in the heap.
  • Native Size: The total amount of native memory used by this object type, in bytes. You'll only see this column when using Android 7.0 and higher. You'll see Java-allocated memory for some objects here because Android uses native memory for some framework classes, such as Bitmap.
  • Shallow Size: The total amount of Java memory used by this object type, in bytes.
  • Retained Size: The total size of memory, in bytes, reserved for all instances of this class.

You can use the two menus above the allocated objects list to select which heap dump to examine and how to organize the data.

From the menu on the left, select the heap to inspect:

  • default heap: When the system does not specify a heap.

  • app heap: The main heap where your app allocates memory.

  • image heap: System boot image, containing classes preloaded during startup. The allocation here ensures that it will never move or disappear.

  • zygote heap: Copy-on-write heap, in which the application process is derived from the Android system.
    From the menu on the right, choose how to schedule assignments:

  • Arrange by class: Groups all assignments based on class name. It's the default value.

  • Arrange by package: Groups all assignments based on package name.

  • Arrange by callstack: Group all allocations into their corresponding call stacks. This option is only effective when capturing a heap dump during recording allocations. Even so, there are likely objects in the heap that were allocated before you started logging, so those allocations are shown first, listing them directly by class name.
    By default, this list is sorted by the Retained Size column. To sort by values ​​in another column, click the column header.

Click on a class name to open the Instance View window on the right (as shown in Figure 6). Each instance listed contains the following information:

  • Depth: The shortest number of hops from any GC root to the selected instance.
  • Native Size: The size of this instance in native memory. You'll only see this column when using Android 7.0 and higher.
  • Shallow Size: The size of this instance in Java memory.
  • Retained Size: The size of the memory dominated by this instance (according to the dominated tree).
    Note: By default, a heap dump does not show you the stack trace for every allocated object. To obtain a stack trace, you must first start recording memory allocations before clicking Capture heap dump. You can then select an instance in the Instance View and view the Call Stack tab next to the References tab, as shown in Figure 6. However, some objects may have been allocated before you started logging allocations, so the call stack for these objects is not displayed. Instances that contain a call stack are represented by a "stack" symbol on the icon. (Unfortunately, you cannot currently view stack traces for heap dumps on Android 8.0, since stack traces require you to perform allocation logging.)

Insert image description here

Figure 6. Timeline indicating the duration required to capture the heap dump

To check your heap, follow these steps:

  • ①Browse the list to find objects with abnormally large heap counts and possible leaks. To help find known classes, click the Class Name column heading to sort alphabetically. Then, click on a class name. The Instance View pane will appear on the right, displaying each instance of the class, as shown in Figure 6.

  • Alternatively, you can quickly find objects by clicking the Filter icon or pressing Ctrl+F (Command+F on Mac) and entering the class or package name in the search field. You can also search by method name if you select Arrange by callstack from the drop-down menu. To use regular expressions, check the box next to Regex. If your search query is case-sensitive, check the box next to Match case.

  • ②In the Instance View pane, click an instance. The References tab will appear below, showing each reference to the object.
    Alternatively, click the arrow next to an instance name to see all its fields, then click a field name to see all its references. To view instance details for a field, right-click the field and select Go to Instance.

  • ③In the References tab, if you find a reference that may be leaking memory, right-click it and select Go to Instance. This selects the corresponding instance from the heap dump, showing you its own instance data.
    In your heap dump, watch for memory leaks caused by any of the following conditions:

  • Long-term references to Activity, Context, View, Drawable, and other objects may keep references to the Activity or Context container.

  • Non-static inner classes that can hold Activity instances, such as Runnable.

  • Objects remain cached longer than necessary.

Save heap dump as HPROF file

After you capture a heap dump, you can view the data in the memory profiler only while the profiler is running. When you exit a profiling session, the heap dump is lost. Therefore, if you want to save a heap dump for later viewing, export it to an HPROF file. In Android Studio 3.1 and earlier, the Export capture to file button is on the left side of the toolbar below the timeline; in Android Studio 3.2 and later, there is an Export capture to file button on the right side of each Heap Dump entry in the Sessions pane. Export Heap Dump button. In the Export As dialog box that appears, save the file with the .hprof file extension.

To use other HPROF analyzers (such as jhat), you need to convert the HPROF file from Android format to Java SE HPROF format. You can do this using the hprof-conv tool provided in the android_sdk/platform-tools/ directory. Run the hprof-conv command with two parameters: the original HPROF file and the location where the converted HPROF file is written. For example:

hprof-conv heap-original.hprof heap-converted.hprof

Import heap dump file

To import an HPROF (.hprof) file, click the Start a new profiling session icon in the Sessions pane, select Load from file, and then select the file from the file browser.

You can also import an HPROF file by dragging it from the file browser to the editor window.

Leak detection in memory analyzer

When analyzing a heap dump in the Memory Profiler, you can filter profiling data that Android Studio believes may indicate memory leaks in Activity and Fragment instances in your app.

The data types displayed by the filter include:

  • An Activity instance that has been destroyed but is still referenced.

  • There is no valid FragmentManager but the Fragment instance is still referenced.
    In some cases, such as the following, the filter may produce false positives:

  • The fragment has been created but has not been used.

  • Fragment is being cached but is not part of FragmentTransaction.
    To use this feature, first capture a heap dump or import a heap dump file into Android Studio. To display fragments and activities that may leak memory, check the Activity/Fragment Leaks checkbox in the memory analyzer's heap dump pane, as shown in Figure 7.

Memory Analyzer: Memory Leak Detection

Insert image description here

Figure 7. Filtering heap dumps to detect memory leaks.

Tips for performance profiling of memory

When using a memory profiler, you should put pressure on your application code and try to force memory leaks. One way to cause a memory leak in your application is to let it run for a while and then check the heap. Leaks may gradually build up in the heap to the top of the allocation. However, the smaller the leak, the longer the application needs to be running in order to detect the leak.

You can also trigger a memory leak in one of the following ways:

  • In different activity states, first rotate the device from portrait to landscape, then rotate it back again, and repeat this rotation multiple times. Rotating the device often causes your app to leak Activity, Context, or View objects because the system recreates the Activity, and if your app holds a reference to one of these objects elsewhere, the system won't be able to garbage collect it.
  • Switch between your app and other apps (navigate to the Home screen and back to your app) in different Activity states.

Tip: You can also use the monkeyrunner testing framework to perform the above steps.

Guess you like

Origin blog.csdn.net/sinat_31057219/article/details/132450378