Summary of Android memory leaks (with memory detection tools)

Summary of Android memory leaks (with memory detection tools)

Memory allocation in Java

It is mainly divided into three parts:

  • Static storage area: It is allocated when compiling and exists during the entire running period of the program. It mainly stores static data and constants.

  • Stack area: When the method is executed, local variables inside the method body will be created in the stack area memory, and the memory will be automatically released after the method ends.

  • Heap area: usually store new objects. Reclaimed by the Java garbage collector.

The difference between stack and heap

The stack memory is used to store local variables and function parameters. It is a first-in-last-out queue, one-to-one correspondence between in and out, no fragmentation, stable and high operating efficiency. When the scope of the variable is exceeded, the variable is invalid, the memory space allocated to it will also be released, and the memory space can be reused.

Heap memory is used to store object instances. The memory allocated in the heap will be automatically managed by the Java garbage collector. Frequent new/delete in the heap memory will cause a lot of memory fragmentation and reduce the efficiency of the program.

Regarding the storage location of non-static variables, we can roughly think:

  • Local variables are located in the stack (the object entity pointed to by the reference variable exists in the heap).

  • Member variables are located in the heap. Because they belong to a class, the class is eventually new into an object and stored in the heap as a whole.

Introduction to the four reference types

The fundamental principle of GC releasing an object is that the object is no longer referenced (strong reference). So what is a strong reference?

Strong Reference

The most commonly used one is strong citations, as follows:

IPhotos iPhotos = new IPhotos(); 

JVM would rather throw OOM than let the GC reclaim objects with strong references. When the strong reference is not used, you can explicitly set all references of the object to null through obj = null, so that the object can be recycled. As for when to recycle, it depends on the GC algorithm, so I won t go into further details here.

Soft Reference

SoftReference softReference = new SoftReference<>(str); 

If an object has only soft references, the garbage collector will not reclaim it when the memory space is sufficient; if the memory space is insufficient, the memory of these objects will be reclaimed. As long as the garbage collector does not collect it, the object can be used.

Soft references were often used for image caching, but Google now recommends LruCache instead, because LRU is more efficient.

In the past, a popular memory cache implementation was a SoftReference
or WeakReference bitmap cache, however this is not recommended.
Starting from Android 2.3 (API Level 9) the garbage collector is more
aggressive with collecting soft/weak references which makes them
fairly ineffective. Addition the in, the Android Prior to 3.0 (the API Level. 11),
The Bitmap WAS A backing of Data Stored in Memory Native Which IS Not
Released in Predictable Manner A, AN Potentially Causing file application
to briefly Exceed ITS Memory Limits and Crash. original

The rough meaning is: because after Android 2.3, GC will be very frequent, resulting in a high frequency of releasing soft references, which will reduce its use efficiency. And before 3.0, Bitmap is stored in Native Memory, and its release is not controlled by GC, so using soft reference to cache Bitmap may cause OOM.

Weak Reference

WeakReference weakReference = new WeakReference<>(str); 

The difference with soft references is that only objects with weak references have a shorter life cycle. Because during GC, once an object with only weak references is found, its memory will be reclaimed regardless of whether the current memory space is sufficient. However, because the garbage collector is a low-priority thread, it may not be able to quickly find those objects that only have weak references--.

PhantomReference

As the name implies, it is the same as a virtual reference. Unlike other types of references, a virtual reference does not determine the life cycle of an object, nor can it obtain an object instance through a virtual reference. Phantom references must be used in conjunction with reference queues (ReferenceQueue). When the garbage collector is about to recycle an object, if it finds that it still has a phantom reference, it will add the phantom reference to the reference queue associated with it before reclaiming the object's memory. The program can know whether the object will be recycled by judging whether there is a phantom reference of the object in the reference queue.

Introduction to Android's garbage collection mechanism

There is a Generational Heap Memory model in the Android system. The system executes different GC operations according to different memory data types in the memory.

The model is divided into three areas:

  • Young Generation

  • Old Generation

  • Permanent Generation

Young Generation

Most new objects are placed in the eden area. When the eden area is full, Minor GC (lightweight GC) is executed, and then the surviving objects are transferred to the Survivor area (there are S0 and S1). Minor GC will also check the objects in the Survivor area and transfer them to another Survivor area, so that there will always be a Survivor area that is empty.

Old Generation

Store long-lived objects (objects that have survived multiple Minor GCs) After the Old Generation area is full, perform Major GC (large GC).

Before Android 2.2, the application thread would be suspended when GC was executed, and a concurrent garbage collection mechanism was added in 2.3.

Permanent Generation

Storage method area. General storage:

  • Information about the class to be loaded

  • Static variable

  • final constant

  • Property and method information

60 FPS

Here is a brief introduction to the concept of frame rate in order to understand why a large number of GCs are easy to cause lag.

When developing an App, it is generally pursued that the frame rate of the interface reaches 60 FPS (60 frames per second). What is the concept of this FPS?

  • You can feel the effect of animation at 10-12 FPS;

  • 24 FPS, you can feel the smooth and coherent animation effect, the common frame rate of movies (not pursuing 60 FPS is to save costs);

  • 60 FPS, to achieve the smoothest effect, for higher FPS, the brain has been difficult to detect the difference.

Android sends out the VSYNC signal every 16 ms to trigger the rendering of the UI (that is, drawing a frame every 16 ms). If the whole process is kept within 16 ms, then a smooth picture of 60 FPS will be achieved. More than 16 ms will cause lag. Then if a lot of GC occurs during UI rendering, or the GC takes too long, it may cause the drawing process to exceed 16 ms and cause stalls (FPS drop, frame drop, etc.), and our brains are very sensitive to the situation of dropped frames Therefore, if memory management is not done well, it will bring a very bad experience to users.

Let me introduce the concept of memory jitter, which may be used later in this article.

Memory thrashing

A large number of new objects in a short period of time triggers GC after reaching the Young Generation threshold, causing the newly-new objects to be recycled. This phenomenon will affect the frame rate and cause freezes.

Memory jitter is roughly represented in the Memory Monitor provided by Android as follows:

Common memory leaks and solutions in Android

Collection leak

If a collection is a global variable (such as static modification), and some objects that take up a lot of memory are directly stored in the collection (rather than stored through weak references), then as the collection size increases, the memory usage will continue to rise. When the Activity etc. are destroyed, these objects in the collection cannot be recycled, causing memory leaks. For example, we like to do some caching through static HashMap. Be careful in this situation. It is recommended to use weak references to access the objects in the collection, and consider manually releasing them when they are not needed.

Memory leak caused by singleton

The static nature of a singleton causes its life cycle to be as long as the application.

Sometimes when creating a singleton, if we need a Context object, there will be no problem if the Context of the Application is passed in. If the Context object of the Activity is passed in, when the Activity life cycle ends, the reference of the Activity is still held by the singleton, so it will not be recycled, and the life cycle of the singleton is as long as the application, so this Caused a memory leak.

Solution 1: Do not directly use the passed-in context in the construction of creating a singleton, but obtain the Application Context through this context. code show as below:

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();// Application  context
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
} 

The second solution: do not need to pass in the context when constructing the singleton, directly write a static method in our Application, return the context through getApplicationContext in the method, and then directly call this static method in the singleton to obtain the context.

Memory leaks caused by non-static inner classes

In Java, non-static inner classes (including anonymous inner classes, such as Handler, Runnable, anonymous inner classes are most likely to cause memory leaks) will hold strong references to external class objects (such as Activity), while static inner classes will not reference External class object.

Non-static inner classes or anonymous classes can access resource attribute member variables of the outer class because they hold references to the outer class; static inner classes cannot.

Because ordinary inner classes or anonymous classes depend on outer classes, you must first create outer classes, and then create ordinary inner classes or anonymous classes; and static inner classes can be created in other outer classes at any time.

Handler memory leaks can pay attention to my other article dedicated to Handler memory leaks: link

WebView leak

WebView in Android has great compatibility problems, and some WebViews even have memory leaks. Therefore, the usual way to cure this problem is to start another process for WebView and communicate with the main process through AIDL. The process where WebView is located can be destroyed at a suitable time according to the needs of the business, so as to achieve the complete release of memory.

Memory leak caused by AlertDialog

new AlertDialog.Builder(this)
        .setPositiveButton("Baguette", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                MainActivity.this.makeBread();
            }
        }).show(); 

The anonymous implementation class of DialogInterface.OnClickListener holds a reference to MainActivity;

In the implementation of AlertDialog, the OnClickListener class will be wrapped in a Message object (see the setButton method of the AlertController class for details), and this Message will be copied internally (you can see it in the mButtonHandler of the AlertController class), Only one of the two messages is recycled, and the other (the Message object referenced by the member variable of OnClickListener) will be leaked!

Solution:

  • This problem does not exist in Android 5.0 and above;

  • Leakage of the Message object cannot be avoided, but if it is just an empty Message object, it will be put into the object pool for later use, there is no problem;

  • Let the DialogInterface.OnClickListener object not hold a strong reference to an external class, such as using a static class;

  • Dismiss the dialog before the Activity exits

Memory leak caused by Drawable

Android has solved this problem after 4.0. You can skip it here.

When our screen rotates, the current Activity will be destroyed by default, and then a new Activity will be created and the previous state will be maintained. In this process, the Android system will reload the UI view and resources of the program. Suppose we have a program that uses a large Bitmap image, and we don't want to reload the Bitmap object every time the screen rotates. The easiest way is to use static decoration for the Bitmap object.

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);
    TextView label = new TextView(this);
    label.setText("Leaks are bad");

    if (sBackground == null) {
        sBackground = getDrawable(R.drawable.large_bitmap);
    }
    label.setBackgroundDrawable(sBackground);

    setContentView(label);
} 

But the above method may cause memory leaks when the screen is rotated, because when a Drawable is bound to a View, the View object will actually become a callback member variable of the Drawable. In the above example, the static sBackground holds A reference to the TextView object, and the TextView holds a reference to the Activity. When the screen rotates, the Activity cannot be destroyed, which causes a memory leak problem.

This problem mainly occurred before 4.0, because the implementation of the setCallback method of Drawable in 2.3.7 and below is direct assignment. Starting from 4.0.1, setCallback uses weak references to deal with this problem, avoiding memory leaks.

Memory leak caused by unclosed resources

  • BroadcastReceiver, ContentObserver and the like are not unregistered

  • Cursor, Stream and the like are not close

  • The infinite loop animation does not stop before the Activity exits

  • Some other releases have no release, and recycles have no recycle... etc.

summary

It is not difficult to find that most of the problems are caused by static!

  • Be careful when using static and pay attention to the references held by the static variable. Use weak references to hold some references when necessary

  • Also pay attention when using non-static inner classes, after all, they hold references to outer classes. (Students who use RxJava should also pay attention to unSubscribe when subscribe)

  • Pay attention to releasing resources at the end of the life cycle

  • When using attribute animations, please stop them when not in use (especially the looping animations), otherwise memory leaks (Activity cannot be released) (View animations will not)

Introduction of several memory detection tools

  • Memory Monitor

  • Allocation Tracker

  • Heap Viewer

  • LeakCanary

Memory Monitor

Located in Android Monitor, this tool can:

  • Conveniently display memory usage and GC status

  • Quickly locate whether the lag is related to GC

  • Quickly locate whether Crash is related to high memory usage

  • Quickly locate potential memory leaks (memory usage has been increasing)

  • But it can t accurately locate the problem

Allocation Tracker

The purpose of the tool:

  • Can locate the object type, size, time, thread, stack and other information allocated in the code

  • Can locate memory jitter problems

  • Cooperate with Heap Viewer to locate the memory leak problem (you can find out where the leaked object was created, etc.)

How to use: There is a Start Allocation Tracking button in the Memory Monitor to start tracking. After clicking to stop tracking, the statistical results will be displayed.

Heap Viewer

This tool is used to:

  • Display memory snapshot information

  • Collect information after each GC

  • A weapon to find memory leaks

How to use: There is a Dump Java Heap button in the Memory Monitor, click it, and select the package classification in the upper left corner of the statistical report. With the initiate GC (execute GC) button of Memory Monitor, memory leaks can be detected.

LeakCanary

The important thing is said three times:

        for (int i = 0; i < 3; i++) {
            Log.e(TAG, " !");
        } 

The specific use of LeakCanary will not go into details, Google it yourself .