To help developers create faster and more efficient applications, we’ve added Android Profiler tools in Android Studio 3.0 and later for CPU, memory, network, and power analysis.

Memory Profiler

Many developers use Memory profilers in the hope of finding and locating Memory leaks. Before I introduce how Memory Profiles address this problem, I want to clarify the concept of “Memory leaks.” Whether or not you currently know about Memory leaks will help me better explain how the Memory Profile works.

A memory leak

What is a memory leak?

In general, we think of a running program that causes a memory leak if an unreachable object still occupies memory. If you’ve ever worked with Pointers in C or C++, you’ll be familiar with this concept.

But in the world of Kotlin and Java, things are a little different. Because both languages run in the Java Virtual Machine (JVM). An important concept in the JVM is garbage collection (GC). When garbage collection runs, the virtual machine first identifies the GC Root. GC Root is an object that can be accessed from outside the heap, either as a local variable or a running thread, etc. The virtual machine identifies all objects that can be accessed from GC Root and they will be retained. Other objects that cannot be accessed from GC root are considered garbage and recycled.

Activity and Fragment leak detection

In Android apps, you should be especially wary of leaking Activity and Fragment objects, because they tend to take up a lot of memory. In Android 3.6, Memory Profiler was added to automatically check for Memory leaks in activities and fragments. Using this feature is very simple:

  • First, you need to save the Heap Dump in the Memory Profiler by clicking the button shown below:

  • After the Heap Dump is loaded, check the “Activity/Fragment Leaks” box:

The Memory Profiler uses the following scenarios to determine if a leak has occurred:

  • When we destroy an instance of an Activity, it is never used again. If there is still a reference to the Activity, the Memory Profiler will assume it has been leaked.
  • An instance of a Fragment should be associated with a Fragment Manager. If we see a Fragment that is not associated with any Fragment Manager and is still referenced, we can also consider a leak.

Note, however, that there is a special case for fragments: if you load the Heap Dump between the time the Fragment was created and the time it was used, the Memory Profiler will generate false positives. The same happens when fragments are cached but not reused.

Other memory leak detection

Memory profilers can also be used to check for other types of leaks, and they provide a lot of information to help you identify if a Memory leak has occurred.

When you have a Heap Dump, the Memory Profiler displays a list of the classes. For each class, the “Allocation” column shows the number of instances. Across it, “Native Size”, “Shallow Size”, and “Retained Size”:

The following figure shows the application memory status of a certain Heap Dump record. Note the red node. In this example, the object represented by this node references the Native object from our project:

Let’s start with the “Shallow Size” column, which is actually quite simple: the amount of memory consumed by the object itself (in this case, the red node itself).

The “Native Size” is also very simple. It is the memory Size consumed by the Native object (blue node) referenced by the class object:

Because once the red node is removed, the remaining orange nodes are not accessible, they are reclaimed by the GC. In this sense, they are held by red nodes, hence the name “Retained Size”.

There is another dimension of data that was not mentioned earlier. When you click on a class name, the screen displays a list of class instances with a new column called “Depth”:

In the case of the red node, if any reference from its left is destroyed, the red node becomes inaccessible and is collected by the garbage collector. For the blue node on the right, you need to destroy the left and right paths if you want it to be garbage collected.

A cautionary note is that if you see an instance with a “Depth” of 1, this means that it is referenced directly by GC root, which also means that it will never be automatically reclaimed.

Here is a sample Activity that implements the LocationListener interface, highlighting the part of the code “requestLocationUpdates” that will register the locationManager with the current Activity instance. If you forget to log out, the Activity leaks. It will always be in memory because the location manager is a GC root and will always be there:

CPU Profiler

Like Memory profilers, CPU profilers provide a way to record and analyze application critical performance data from another perspective.

To use a CPU Profiler, first generate some CPU usage records:

  • Go to the CPU Profiler screen in Android Studio and click the “Record” button when your application is already deployed.
  • Perform the actions you want to analyze in the application;
  • To return to the CPU Profiler, click the “Stop” button.

Since the final rendered data is organized on a thread basis, you should make sure that the correct thread is selected before viewing the data:

Call Chart

In the Bottom half of the CPU Profiler interface, there are four tabs that correspond to four different data charts: Call Chart, Flame Chart, Top Down, and Bottom Up. Call Chart, perhaps the most straightforward of these, is basically a reorganization and visual rendering of the Call stack:

Flame Chart

Flame Chart provides an aggregate of call stack information. Unlike Call Chart, its horizontal axis displays percentage values. Because the timeline information is omitted, Flame Chart shows the percentage of the total recorded time consumed per call. The vertical axis is also switched, showing the caller at the top and the caller at the bottom. The chart now looks narrower and narrower as it goes up, like a flame, hence the name:

Top Down Tree

The two charts described above help us see the big picture from two perspectives. If we need more accurate time information, we need to use the Top Down Tree. In the CPU Profiler, the Top Down TAB displays a table of data, and to help you understand the meaning of each set of data, we will try to build a Top Down Tree.

Building a Top Down Tree is not complicated. Based on Flame Chart, you just need to start with the caller and keep adding the called as child nodes until the entire Flame Chart has been traversed and you have a Top Down Tree:

  • Self Time – how long it takes to run your own code;
  • Children Time – Time to call other methods;
  • Total Time – the sum of the previous two times.

With the Top Down Tree, we can easily summarize the three groups of information into a table:

We start with node A:

  • It takes A second for A to run its own code, so Self Time is 1;
  • It then takes 9 seconds to call other methods, which means its Children Time is 9;
  • This takes 10 seconds, and the Total Time is 10;
  • B and D and so on…

It is worth noting that the D node simply calls C and does nothing on its own, which is common in method encapsulation. So the Children Time and Total Time of D are both 2.

The following is a full expansion of the table. When analyzing your application in Android Studio, the CPU Profiler does all of the above calculations, just understand how the numbers are generated:

Bottom Up Tree

The Bottom Up Tree comes in handy when you want to easily find the call stack for a method. As the name suggests, the Bottom Up Tree starts at the Bottom, so we can build the Tree backwards by constantly adding callers to the node. Since each individual node can build a tree, this is actually a Forest:

The table has four rows because we have four trees in the forest. Starting from node C:

  • Self Time is 4 + 2 = 6 seconds;
  • C calls no other methods, so Children Time is 0;
  • Add the previous two together, and the total time is 6 seconds.

It looks just like Top Bottom Tree. Next, expand the C node and calculate the case for the callers B and D of C.

When calculating the relative time of nodes B and D, the situation is different from the previous Top Bottom Tree:

  • Since we are building a Bottom Up Tree based on C nodes, all the time information is also based on C nodes. Now, when we calculate B’s Self Time, we should calculate the Time when C is called by B, not the Time when B itself executes, which is 4 seconds; For D, it’s 2 seconds.
  • Since only B and D call C’s method, their Total Time sum should be equal to C’s Total Time.

The next Tree is the Bottom Up Tree of node B, its Self Time is 3 seconds, Children Time is the Time to call other methods, there’s only C, so it’s 2 seconds. Total Time is always the sum of the first two. Here’s what the table looks like unfolded:

Cheat sheet

Four different data graphs were introduced and some details were explained about how some of the data was calculated. If you find it hard to remember too many clues, don’t worry, here is a concise cheat sheet for you:

conclusion

This article introduced two data analysis tools in the Android Studio Profiler.

Memory profilers can automatically detect Memory leaks in activities and fragments, and other types of Memory leaks can be found and solved by understanding and using the data provided by the data analysis function in Memory profilers.

The CPU Profiler presents data in four dimensions: Call Chart, Flame Chart, Top Down, and Bottom Up.

Hopefully, this has helped you understand Android Profiler a little better. If you still have any questions, feel free to leave a comment below. We also welcome feedback from Android Studio on any problems we encounter.

You can also review the Android Developer Summit 2019 presentation with this video:

Video link: v.qq.com/x/page/m302…

Click here toRead more about application performance right now