MAT-memory leak tool usage

Table of contents

1. Introduction to MAT

1.1 Introduction to MAT

1.2 Download and installation of MAT tool

2. Basic process of using MAT

2.1 Obtain HPROF file

2.2 Introduction to MAT main interface

2.3 Introduction to concepts in MAT

2.3.1 Shallow heap

2.3.2 Retained Heap

2.3.3 GC Root

2.4 Some commonly used views in MAT

2.4.1 Thread OvewView

2.4.2 Group

2.4.3 Path to GC Root

3. MAT usage skills

3.1 Use Android Studio to Dump memory files

3.2 Unreachable objects

3.3 Histogram

3.3.1 Thread information

3.4 Help information

3.5 Debug Bitmap

3.6 Debug ArrayList

3.7 Big Drops In Dominator Tree

4. Common cases that easily cause memory leaks in Android (Java)

4.1 Android memory

4.2 Common improper memory usage situations

4.2.1 Querying the database does not close the cursor

4.2.2 When constructing the Adapter, the cached convertView is not used

4.2.3 Call recycle() to release memory when the Bitmap object is no longer in use

4.2.4 Release object reference

4.2.5 Characteristics of Java memory leaks

4.2.6 Others


1. Introduction to MAT

1.1 Introduction to MAT

MAT (Memory Analyzer Tool), a memory analysis tool based on Eclipse, is a fast, feature-rich JAVA heap analysis tool that can help us find memory leaks and reduce memory consumption. Use memory analysis tools to analyze numerous objects, quickly calculate the size of objects in memory, see who is blocking the garbage collector's recycling work, and visually view the possible results through reports. Object.

Of course, MAT also has an independent version that does not rely on Eclipse. However, when debugging Android memory, this version needs to convert the file generated by DDMS before it can be opened on the independent version of MAT. However, this Tools is already provided in the Android SDK, so it is very convenient to use.

1.2 Download and installation of MAT tool

Here is the download address of MAT: https://download.eclipse.org/mat/1.4/update-site/

2. Basic process of using MAT

2.1 Obtain HPROF file

The HPROF file is a file that MAT can recognize. The HPROF file stores a memory snapshot of the java process at a specific point in time. There are different formats for storing this data, generally containing the state of the Java objects and classes in the heap when the snapshot was triggered. Since the snapshot only lasts a moment, the heap dump cannot contain information such as when and where (in which method) an object was allocated.
This file can be exported using Profiler:

  1. Open Proflier, select MEMORY->Capture heap dump->Record or take the HPROF file and open it with MAT

2.2 Introduction to MAT main interface

What is introduced here is not the main interface of the MAT tool, but the OverView interface that is displayed after importing a file.

  • Open the converted hprof file:

If the first one is selected, a report will be generated. This is no big deal.

  • Select OverView interface:

What we need to focus on is the Actions area below

  • Histogram: List the objects in memory, the number and size of objects

  • Dominator Tree: Lists the largest objects and the objects they depend on to survive (the sizes are sorted based on Retained Heap)

  • Top Consumers: List the largest objects graphically

  • Duplicate Class: Automatically analyze the causes of leaks through MAT

Generally, Histogram and Dominator Tree are the most commonly used.

2.3 Introduction to concepts in MAT

To understand the list information of MAT, you must understand the concepts of Shallow heap, Retained Heap, and GC Root.

2.3.1 Shallow heap

Shallow size is the size of the memory occupied by the object itself, excluding the objects it refers to.

  • The Shallow size of a regular object (non-array) is determined by the number and type of its member variables.
  • The shallow size of an array is determined by the type of array elements (object type, basic type) and the length of the array.

Because unlike C++ objects that can store a large amount of memory, Java's object members are all references. The real memory is all on the heap, and it looks like a bunch of native byte[], char[], and int[]. So if we only look at the memory of the object itself, the amount is very small. So we see that the Histogram is sorted by Shallow size, with byte and char ranked first and second.

2.3.2 Retained Heap

The concept of Retained Heap means that if an object is released, the heap size occupied by all objects that are referenced and released (including recursively released) will be reduced due to the release of the object. Therefore, if a member of an object newes a large int array, then this int array can also be calculated into the object. Compared with shallow heap, retained heap can more accurately reflect the actual size occupied by an object (because if the object is released, retained heap can be released).

What I want to say here is that Retained Heap is not always effective. For example, I create a new piece of memory in A and assign it to a member variable of A. At this time, I let B also point to this memory. At this time, because both A and B refer to this memory, the memory will not be released when A is released. So this memory will not be calculated into the Retained Heap of A or B. To correct this, the Leading Object (such as A or B) in MAT is not necessarily just one object, but can also be multiple objects. At this time, the Retained Set of the combination (A, B) contains the large memory. Corresponding to the UI of MAT, in the Histogram, you can select Group By class, superclass or package to select this group.

In order to calculate Retained Memory, MAT introduces Dominator Tree. Add object A to reference B and C, and both B and C refer to D (a diamond). At this time, Retained Memory needs to be calculated. A includes A itself and B, C, and D. Because B and C both reference D, their Retained Memory is just themselves. D is of course just myself. I think in order to speed up calculations, MAT changed the object reference graph and converted it into an object reference tree. In this example, the root of the tree is A, and B, C, and D are his three sons. B, C, D are no longer related to each other. By turning the reference graph into a reference tree, it will be very convenient to calculate the Retained Heap and display it. Corresponding to the MAT UI, in the dominator tree view, the shallow heap and retained heap of each object are displayed. Then you can use this node as the root of the tree and refine it step by step to see where the retained heap is used. What I want to say is that this conversion from graph to tree does facilitate memory analysis, but sometimes it makes people a little confused. Originally object B is a member of object A, but because B is also referenced by C, B is not below A in the tree, but is probably at the same level.

To correct this, right-click in MAT and select with outgoing references and with incoming references in List objects. This is a real reference graph concept,

  • outgoing references: Represents the outgoing node of the object (the object referenced by the object).
  • incoming references: Indicates the incoming node of the object (the object that references the object).

In order to better understand Retained Heap, here is an example to illustrate:

Think of objects in memory as nodes in the figure below, and objects refer to each other. There is a special node GC Roots, which is the starting point of the reference chain:

Starting from obj1, the blue nodes in the above figure represent objects that can only be accessed directly or indirectly through obj1. Because it can be accessed through GC Roots, obj3 in the left picture is not a blue node; but in the right picture it is blue because it is already included in the retained collection.
So for the left picture, the retained size of obj1 is the sum of the shallow sizes of obj1, obj2, and obj4; the
retained size of the right picture is the sum of the shallow sizes of obj1, obj2, obj3, and obj4.
The retained size of obj2 can be calculated in the same way.

2.3.3 GC Root

When the GC finds that an object cannot be accessed through any reference chain, the object is recycled. The noun GC Roots is the starting point for analyzing this process. For example, the JVM itself ensures the reachability of the object (then the JVM is GC Roots), so GC Roots maintains the reachability of the object in the memory. Once it is unreachable, that is Be recycled. Usually GC Roots is an object (such as method parameters and local variables) on the call stack of the current thread (current thread), or the thread itself or a class loaded by the system class loader (system class loader) and Active objects retained by native code. Therefore, GC Roots is a powerful tool for analyzing why objects are still alive in memory.

2.4 Some commonly used views in MAT

2.4.1 Thread OvewView

Thread Overview can view the Thread information of this application:

2.4.2 Group

In the Histogram and Domiantor Tree interfaces, you can choose to display the results in another Group mode (the default is Group by Object). By switching to Group by package, you can better see which class in the package takes up the most memory, and also It's easy to locate your own application.

2.4.3 Path to GC Root

Right-click on an entry in the Histogram or Domiantor Tree to view its GC Root Path:

Here we also need to explain Java's reference rules:
from the strongest to the weakest, different reference (reachability) levels reflect the life cycle of the object.

  • Strong Ref (strong reference): Usually the code we write is Strong Ref, which corresponds to strong reachability. Only when strong reachability is removed can the object be recycled.
  • Soft Ref (soft reference): Corresponding to soft reachability, as long as there is enough memory, the object will be kept until the memory is found to be tight and there is no Strong Ref before the object is recycled. Generally, it can be used to implement caching, which is implemented through the java.lang.ref.SoftReference class.
  • Weak Ref (weak reference): Weaker than Soft Ref, when it is found that there is no Strong Ref, the object is recycled immediately without having to wait until the memory is tight. Implemented through the java.lang.ref.WeakReference and java.util.WeakHashMap classes.
  • Phantom Ref (virtual reference): No object is maintained in memory at all, you can only use the Phantom Ref itself. Generally used to perform a special cleanup process after entering the finalize() method, implemented through java.lang.ref.PhantomReference.

点击Path To GC Roots –> with all references

3. MAT usage skills

3.1 Use Android Studio to Dump memory files

The latest version of Android Studio can directly obtain the hprof file:

Then select the file, right-click to convert it to a standard hprof file, and you can open it in MAT.

Before using Eclipse or Android Studio to capture memory, you must manually click the Initiate GC button to manually trigger GC, so that the memory usage captured does not include Unreachable objects.

3.2 Unreachable objects

Unreachable refers to objects that can be recycled by the garbage collector, but because no GC occurs, they are not released. The Unreachable objects in the memory usage captured at this time are these objects.

After clicking Calculate Retained Size, the Retained Size column will appear.

You can see that the Retained Heap values ​​of Unreachable Object objects are all 0. This is also normal.

3.3 Histogram

The main function of Histogram in MAT is to check the number of instances. It is generally used to check the number of instances of the class you created.

  • You can easily find out the objects that occupy the most memory and sort them according to Percentage.
  • You can view the Dominator Tree view of objects in different dimensions. Group by class, Group by class loader, Group by package
    and Histogram are similar. Over time, overflow objects can also be found through multiple comparisons.
  • The difference between Dominator Tree and Histogram is that the perspective of the station is different. The Histogram is viewed from the perspective of the class, while the Dominator Tree is viewed from the perspective of the object instance of the station. The Dominator Tree can more easily see its reference relationship.

By looking at the number of Objects and combining the code, you can find classes with memory leaks ( that is, objects that are reachable but useless, or objects that can be reused but recreated )

Objects can also be grouped in Histogram, making it easier to view object information in your own Package.

3.3.1 Thread information

You can view the current Thread information in MAT:

Information that can be obtained from the figure:

  1. You can see Threads that may have memory problems:

  2. You can see the number of Threads that may have problems

3.4 Help information

In each view in MAT, many options will appear when you right-click on each Item. Many times we need to rely on these options for analysis:

The specific meaning of these options can be searched and viewed through the Search Queries option in the right-click (the fourth option from the last in the picture above), which is very useful.

As you can see, all commands are actually configuring different SQL query statements.

For example, our most commonly used:

  • List objects -> with incoming references : View external object references held by this object
  • List objects -> with outcoming references : Check which external objects this object is referenced by
  • Path To GC Roots -> exclude all phantim/weak/soft etc. references : View the GC Root of this object. It does not include virtual, weak references, and soft references. The rest are strong references. From a GC point of view, except for strong references, other references can be removed by GC if the JVM needs it. If an object cannot be GCed, it is because of the existence of strong references, which will cause it to be deleted during the GC process. It cannot be recycled, so the memory overflows.
  • Path To GC Roots -> exclude weak/soft references : View the GC Root of this object, excluding all references from weak references and soft references.
  • * Merge Shortest path to GC root * : Find the common path from the GC root node to an object or a group of objects

3.5 Debug Bitmap

If you often use MAT to analyze memory, you will find that the memory occupied by Bitmap is very large, which is related to its actual display area. On a 2K screen, a Bitmap can reach 20MB in size.

So if MAT provides a method, you can export the byte array storing Bitmap and open it using a third-party tool. This greatly improves our efficiency in analyzing memory leaks.

3.6 Debug ArrayList

ArrayList is a very commonly used data structure. In MAT, if you want to know what data is in ArrayList, you need to right-click -> List Objects -> With outgoing references, and then you can see the following picture:

As you can see from the picture above, the content of this ArrayList is in an array array, which exposes the internal structure of the ArrayList. It is a bit inconvenient to view, so MAT provides another way to view the data in the ArrayList:

The result is very intuitive:

3.7 Big Drops In Dominator Tree

Big Drops In Dominator Tree option is right click ->

Displays memory accumulation points in the dominator tree. Displayed are objects with a big difference between the retained size of the parent and the children and the first “interesting” dominator of the accumulation point. These are places where the memory of many small objects is accumulated under one object.

4. Common cases that easily cause memory leaks in Android (Java)

4.1 Android memory

Before using the MAT tool, you must have a basic understanding of Android's memory allocation method, and you must also be sensitive to codes that can easily cause memory leaks. Troubleshooting memory leaks at the code level will help memory usage.

Android is mainly used in embedded devices. Due to some well-known conditions, embedded devices usually do not have high configurations, especially the memory is relatively limited. If there are too many improper uses of memory in the code we write, it will inevitably make our device run slowly or even crash. In order to enable Android applications to run safely and quickly, each Android application will use a dedicated Dalvik virtual machine instance to run, which is hatched by the Zygote service process, which means that each application is Runs in its own process. On the one hand, if a memory leak occurs during the running of a program, it will only cause its own process to be killed, without affecting other processes (if there is a problem with system processes such as system_process, it will cause the system to restart). On the other hand, Android allocates different memory usage upper limits for different types of processes. If the memory used by an application process exceeds this upper limit, it will be regarded as a memory leak by the system and will be killed.

4.2 Common improper memory usage situations

4.2.1 Querying the database does not close the cursor

Description:
Querying the database is often performed in the program, but it is often the case that the Cursor is not closed after use. If our query result set is relatively small, the memory consumption is not easy to detect. The memory problem will only occur when a large number of operations are performed for a long time, which will bring difficulties and risks to future testing and troubleshooting.
Sample code:

1
2
3
4

Cursor cursor = getContentResolver().query(uri ...);
if (cursor.moveToNext()) {
... ...
}

Corrected example code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Cursor cursor = null;
try {
cursor = getContentResolver().query(uri ...);
if (cursor != null && cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}

`

4.2.2 When constructing the Adapter, the cached convertView is not used

Description: Taking the BaseAdapter that constructs ListView as an example, the BaseAdapter provides methods:

1

public View getView(int position, View convertView, ViewGroup parent)

To provide the view object required for each item to the ListView. Initially, ListView will instantiate a certain number of view objects from BaseAdapter based on the current screen layout, and ListView will cache these view objects. When the ListView is scrolled upward, the view object of the original top list item will be recycled and then used to construct the new bottom list item. This construction process is completed by the getView() method. The second formal parameter of getView(), View convertView, is the view object of the cached list item (if there is no view object in the cache during initialization, convertView is null).
It can be seen from this that if we do not use convertView, but re-instantiate a View object in getView() every time, it will waste resources and time, and will also make the memory usage larger and larger. The process of ListView recycling the view object of list item can be viewed at: android.widget.AbsListView.java –> void addScrapView(View scrap) method.

Sample code:

1
2
3
4
5

public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}

Sample correction code:

1
2
3
4
5
6
7
8
9
10
11
12

public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
populate(view, getItem(position));
...
} else {
view = new Xxx(...);
...
}
return view;
}

4.2.3 Call recycle() to release memory when the Bitmap object is no longer in use

Description: Sometimes we manually operate Bitmap objects. If a Bitmap object occupies a large amount of memory, when it is no longer in use, the Bitmap.recycle() method can be called to recycle the memory occupied by the pixels of this object.
In addition, when developing the latest version of Android, you can also use the following method to release the memory occupied by this Bitmap

1
2
3
4
5

Bitmap bitmap ;
...
bitmap initialization and use
...
bitmap = null;

4.2.4 Release object reference

Description: This situation is difficult to describe. Let’s give two examples to illustrate.

Example A:
Assume the following operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class DemoActivity extends Activity {
... ...
private Handler mHandler = ...
private Object obj;
public void operation() {
obj = initObj();
...
[Mark]
mHandler.post(new Runnable() {
public void run() {
useObj(obj);
}
});
}
}

我们有一个成员变量 obj,在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码中,即便是mHandler所在的线程使用完了obj所引用的对象,但这个对象仍然不会被垃圾回收掉,因为DemoActivity.obj还保有这个对象的引用。所以如果在DemoActivity中不再使用这个对象了,可以在[Mark]的位置释放对象的引用,而代码可以修改为:

1
2
3
4
5
6
7
8
9
10
11

public void operation() {
obj = initObj();
...
final Object o = obj;
obj = null;
mHandler.post(new Runnable() {
public void run() {
useObj(o);
}
}
}

Example B:
Suppose we want to monitor the phone service in the system to obtain some information (such as signal strength, etc.) in the lock screen interface (LockScreen), then we can define a PhoneStateListener object in LockScreen and register it with the TelephonyManager service. middle. For the LockScreen object, a LockScreen object will be created when the lock screen interface needs to be displayed, and the LockScreen object will be released when the lock screen interface disappears.

But if we forget to cancel the PhoneStateListener object we registered before when releasing the LockScreen object, the LockScreen will not be garbage collected. If the lock screen interface is continuously displayed and disappeared, OutOfMemory will eventually be caused because a large number of LockScreen objects cannot be recycled, causing the system_process process to hang.

In short, when an object A with a short life cycle is referenced by an object B with a long life cycle, when the life cycle of A ends, the reference to A must be cleared in B.

4.2.5 Characteristics of Java memory leaks

● The main characteristics of memory leaks in Java: reachable, useless
● Useless means that it is created but not released after it is no longer used
● It can be reused but a new object is created for processing

4.2.6 Others

The most typical situation in Android applications where you need to pay attention to releasing resources is in the life cycle of the Activity, when resources need to be released appropriately in the onPause(), onStop(), and onDestroy() methods. Since this situation is very basic, we will not explain it in detail here. For details, you can check the official document's introduction to the Activity life cycle to clarify when and which resources should be released.

Some other examples will be added in supplementary versions.

Guess you like

Origin blog.csdn.net/weixin_47465999/article/details/128590456