preface

Memory leaks are a common problem in Android development, and tracing and troubleshooting their root causes is a skill that advanced programmers must possess. Children today will share with you some of the views in this respect, if there is misunderstanding or different views, you can leave a message in the comment section we discuss, if you like to give a thumbs-up to encourage it.

The length is longer, you can find what you need to know through the catalog

directory

1, JAVA memory parsing 2, JAVA reclaim mechanism 3, four kinds of references 4, summary 5, Android memory leak detection tool 6, memory leak check and solution process 7, common causes of memory leak

1. JAVA memory parsing

To understand a memory leak, you need to understand how runtime memory is structured in Java to know where it is causing it. Without further ado, picture aboveJava memory at runtime is divided into two large chunks:Thread private(Blue area),Shared data area(Yellow area)

Thread private: mainly used to store some information private to each thread, including: program counter, virtual machine stack, local method stack

Shared data area: mainly used to store some common information, including: method area (including constant pool), heap

  1. Program counter: Lets each thread in the program know which line it needs to execute next. Multithreading in Java is preemptive (because the CPU only executes one thread at a time), and when a thread switches, the program counter tells you which line to continue.




















  1. Virtual machine stack: Describes the memory model of Java methods. The “stack” is actually the virtual machine stack, which has the same life cycle as a thread. When each method (excluding native method) is executed, a stack frame will be created to store information such as local variable table, operand stack, dynamic link, method exit and so on.











  1. Native method stack: provides memory space for Native methods used by virtual machines. Some virtual machine implementations, such as the mainstream HotSpot VIRTUAL machine, simply merge the local method stack with the virtual machine stack.











  1. Method area: mainly stores loaded class information (loaded by the ClassLoader), constants, static variables, compiled code some information. GC is less present here in this region.

  2. Heap: Holds almost all object instances and array data. It is the largest piece of memory managed by a virtual machine and the main battleground of GC, so it is also called “GC heap” or “garbage heap”.

It’s worth mentioning that in the Java Virtual Machine specification, an exception (1) OutOfMemoryError is defined here

  1. Runtime constant pool: Part of the “method area” that holds the various literal and symbolic references generated by the compiler.

Literals: Similar to constants at the Java language level, they contain text strings, constant values declared final, and so on. Symbolic references: compiled language-level concepts, including (1) fully qualified names of classes and interfaces, (2) field names and descriptors, and (3) method names and descriptors

2. JAVA recycle mechanism

In Java, memory is collected through Garbage Collection. How does the JVM determine whether an object can be collected? Here we need to talk about its recycling algorithm

(1) Reference counting algorithm

Reference counting was an early strategy in the garbage collector. In this approach, there is a reference count for each object instance in the heap. When an object is created and the instance of the object is assigned to a variable, the count of the variable is set to 1. When any other variable is assigned a reference to this object, the count increases by 1 (a = b, then the counter of the object instance referenced by B +1), and when a reference to an object instance expires or is set to a new value, the object instance’s reference counter decreases by 1. Any object instance that references a counter of 0 can be garbage collected. When an object instance is garbage collected, the reference counter of any object instances it references decreases by one.

Advantages: Reference counting collectors can be quickly executed, interwoven with program execution. This is good for real-time environments where programs need to be free from prolonged interruptions.

Disadvantages: Circular references cannot be detected. If the parent object has a reference to the child object, the child object in turn references the parent object. Thus, their reference count can never be zero. For example, in the code snippet below, the last Object instance is out of our code’s control, but its reference is still 1, and memory leaks.

/** **/
Object o1 = new Object()      // A reference to Object +1, in which case the counter is 1
Object o2;
o2.o  = o1;   			      // A reference to Object is +1, where the counter is 2
o2 = null;
o1 = null;				      // Reference to Object -1, in which case the counter is 1
Copy the code

(2) Accessibility analysis algorithm

Accessibility analysis algorithm is now the mainstream of the Java method, through a series of GC ROOT as the starting point, starting from a GC ROOT, find the corresponding reference node, found after this node, continue to look for reference from the node, when all the reference node to find after, the rest of the nodes is considered not by reference to the node, That is, the useless nodes (ObjD, ObjE, ObjF in the graph). Thus, instant reference loops do not cause leakage.

Objects that can be used as GC Root in Java are:

Objects referenced by static properties in the method area 2. Objects referenced by constants in the method area 3. Objects referenced by Native objects in the JNI method stack 4

However, unreachable objects in the reachability analysis algorithm do not have to be reclaimed. When the GC first sweeps through these objects, they are in a “death-wait” phase. To actually carry out an execution, at least two tagging procedures are required. If an object is found to have no reference chain associated with GC Roots after reachability analysis, it will be tagged for the first time and then go through a filter, and the Finalize method of the object will be executed. If the object does not overwrite Finalize or has already been executed. The VIRTUAL machine will not execute the Finalize method. Finalize is the last chance for objects to escape.

3. Four kinds of quotations

At the end of the day, memory leaks are caused by improper handling of references. So, we need to talk a lot about the four types of references in Java: strong weak weak weak.

(1) Strong reference: Generally we use Strong reference, for example: Object o = new Object(); As long as the strong reference exists, the garbage collector does not reclaim the referenced object.

(2) Soft Reference: used to define some objects that are useful but not necessary. For objects associated with soft references, the system will recycle them for a second time before they run out of memory. If they run out of memory, the system will throw out of memory. (that is, when memory is tight, its soft reference is reclaimed)

(3) Weak Reference: used to describe non-essential objects. Objects associated with weak references can only survive until the next garbage collection occurs. When garbage collector collects, objects associated with weak references are reclaimed regardless of whether memory is sufficient. (That is, weak references are swept away by GC)

(4) Phantom Reference: also known as Phantom Reference or Phantom Reference, it is the weakest Reference relation. A virtual reference to an object does not affect its lifetime at all, nor can an object instance be obtained from a virtual reference. The only purpose of a virtual reference is to receive a system notification when the object is GC.

Soft versus weak references

If you just want to avoid OutOfMemory exceptions, you can use soft references. If you are more concerned about the performance of your application and want to reclaim some memory-consuming objects as quickly as possible, you can use weak references. You can also choose soft or weak references based on how often the object is used. If the object is likely to be used frequently, use soft references. If it is more likely that the object will not be used, use weak references.

4, summary

At this point, we know that memory leaks occur because long-lived objects in heap memory hold references to short-lived objects that cannot be reclaimed because long-lived objects hold references to them, even though the short-lived objects are no longer needed.

5. Android Memory leak detection tool

This section Outlines the tools you need to borrow to troubleshoot memory leaks. Skip this section if you are already familiar with the tools.

(1) Android Profiler

This tool comes with Android Studio and can check CPU usage, memory usage, and network usage. It replaces Android Monitor in Android Studio3.0① Enforce the button for the garbage collection event.

② Capture heap dump button.

③ Record the button of memory allocation.

④ The button to enlarge the timeline.

⑤ Jump to the real-time memory data button.

⑥ Event timeline displays activity status, user input events and screen rotation events.

⑦ Memory usage schedule, including the following:

• A stack diagram of how much memory is used by each memory category, as shown on the Y-axis on the left and the color keys at the top.

• Dashed lines indicate the number of allocated objects, as shown on the Y-axis on the right.

• ICONS for each garbage collection event.

(2) MAT (Memory Analyzer Tool)

MAT is used to locate leaks. Because we know the leak from Android Profiler, but it is difficult to lock the specific place causing the leak, so WE use MAT to lock it. We will introduce the specific use with an example and Android Profiler later.

Download address: www.eclipse.org/mat/downloa…

6. Process of checking and solving memory leaks

After the previous section of the theory, may be a lot of small partners are a little impatient, now to the real operation.

Tips: theory is the necessary support for progress, otherwise just know how and don’t know why.

(1) Step 1: Mine-clearing operation of detection function

When we need to check a module, or where the app has a memory leak, sometimes will be a loss, some of the feeling of looking for a needle in a haystack, leakage, after all, not every page, and sometimes will lead to leakage is a function, so we can adopt “mine clearance operations”, also is the need to check the page and the function of first use a casually, For example, if the MainActivity leak is checked, you can log in to the MainActivity, log out, and log in to the MainActivity again.

(2) Step 2: Get the memory snapshot with Android Profiler

Use the Android Profiler’s GC feature to force garbage collection and dump memory (button 2 in the “Introduction to Android Profiler” figure). Then wait for a period of time, the red box in the picture will appear:In fact, it is difficult to intuitively obtain the data of memory analysis on the page obtained here. At most, we can only select “Arrange by Package” to sort by package, and then enter the package to check whether the number of activity references in the application is normal, so as to determine whether it is properly recycled

Illustration of the columns in the figure

Alloc Cout: Retained Size of the object Shallow Size Retained Set: Retained memory Size of the reference group (including other objects referenced by the same object)

(3) Step 3: Analysis with Android Studio

At this point, we still don’t have intuitive memory analysis data, so we need to use more specialized tools. We will now save the memory snapshot we just took as a hprof file through the buttons in the red box in the figure below.

Drag the saved Hprof file into the AS, check “Detect Leaked Activities”, and click the green button for analysis.If there is a memory leak, the following picture will appear. You can clearly see that MainActivity leaks here. We also observed that the MainActivity might have more than one object. Maybe there was a leak when we quit the program last time, so it could not be recycled. When you open the app here, the system will create a new MainActivity. But so far, we only know that MainActivity leaks, and we don’t know exactly where MainActivity leaks, so we need to use MAT for further analysis. (4) Step 4: Hprof file conversion

Before using MAT to open the hprof file, the hprof file saved just now should be converted. From the terminal, with the conversion tool hprof-conv (in SDK /platform-tools/hprof-conv), use the command line:

hprof-conv -z src dst
Copy the code

-z: excludes memory that is not app. For example, Zygote SRC: indicates the path of the hprof file to be converted. DST: indicates the path of the converted file (file suffix or.hprof).

(5) Step 5: Conduct specific analysis through MATOpen the converted hprof file in MAT, as shown belowWhen opened, you will see the following imageTo analyze this, we need to go into the “Histogram” and click the button shown in the image belowWhen you open the “Histogram” Histogram, you should see the image below, entering the leaking classes observed in AS in the red box, such AS MainActivity AS seen aboveThen merge the search results and exclude “soft”, “weak” and “virtual” reference objects. Right-click the search results and choose the options in the picture belowThe combined results are as followsMainActivity is caused by a hashMap in com.netease.nimlib.g. E. Class E belongs to a third party library. For example, forget unbinding operations. The specific interruption of this holding needs to be analyzed according to its own code. The problem in the example is caused by the failure to log out on the exit page after using the third-party library registration.

After solving the problem, we can take another round of memory snapshot until there is no memory leakage. The process will be boring, but solving the leakage bit by bit will give the APP a qualitative leap.

Common causes of memory leaks

(1) Collection class

A collection class that only has a method to add elements without a corresponding deletion mechanism will consume memory. If the collection class is a global variable (static attributes in the class, global maps, etc., have static references or final references to it all the time), then the absence of a deletion mechanism is likely to cause the memory footprint of the collection to increase.

(2) Singleton mode

Incorrect use of the singleton pattern is a common problem that causes memory leaks. After being initialized, a singleton persists throughout the lifetime of the JVM (as a static variable). If a singleton holds a reference to an external object, that external object cannot be properly recycled by the JVM, resulting in memory leaks.

public class SingleTest{
      private static SingleTest instance;
      private Context context;
      private SingleTest(Context context){
          this.context = context;
      }
      public static SingleTest getInstance(Context context){
          if(instance ! =null){
                instance = new SingleTest(context);
          }
          returninstance; }}Copy the code

If you pass an Activity as a Context to get a singleton object, the singleton holds a reference to the Activity, so the Activity cannot be released. Do not directly reference the Activity as a member variable; use Application if you allow. If you have to use an Activity as a Context, you can use a WeakReference. Similarly, for services and other objects with their own life cycles, direct references need to be carefully considered for the possibility of memory leaks.

(3) Resources are not closed or released

BroadcastReceiver, ContentObserver, FileObserver, Cursor, A Callback must be unregistered or closed after the Activity onDestroy or the end of a class lifecycle, otherwise the Activity class will be strongly referenced by the system and will not be recycled. It is important to note that closed statements must be closed in finally, otherwise the activity may leak because the exception does not close the resource

(4) Handler

As long as the Message sent by the Handler has not been processed, the Message and the Handler object that sent it are held by the thread MessageQueue. In particular, handlers perform deferred tasks. Handler should be used with great care, as it can easily lead to memory leaks.

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //do something}};private void loadData(a){
        //do request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); }}Copy the code

Since mHandler is an instance of Handler’s non-statically anonymous inner class, it holds a reference to the external Activity class. We know that message queues are polling for messages in a Looper thread. Message in the Message queue holds a reference to the mHandler instance, which in turn holds a reference to the Activity. As a result, the Activity’s memory resources cannot be recycled in time. Causes a memory leak, so the alternative is:

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private void loadData(a) {
        //do request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<Context>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = (MainActivity) reference.get();
            if(mainActivity ! =null) {
                //do something to update UI via mainActivity}}}@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); }}Copy the code

Create a static Handler inner class, and then use a weak reference to the object that the Handler holds, so that it can reclaim the object that the Handler holds. This avoids Activity leakage, but the Looper thread may still have messages waiting to be processed in its message queue. So we should remove messages from the message queue when we Destroy or Stop the Activity,

@Override
protected void onDestroy(a) {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}
Copy the code

Using mHandler. RemoveCallbacksAndMessages (null); Remove all messages and all Runnable from message queue. You can also use mHandler.removecallbacks (); Or mHandler. RemoveMessages (); To remove the specified Runnable and Message.

(5) the Thread

Along with handlers, threads are an important source of memory leaks. The main cause of memory leaks is the uncontrollable life cycle of threads. For example, if a thread is an internal class of an Activity, the thread object holds a reference to the Activity. If the thread’s run function takes a long time to complete, the thread object is not destroyed. Therefore, the old Activity referenced by the thread is not destroyed.

(6) System bugs

InputMethodManager, for example, holds activities without releasing them, causing leaks that need to be interrupted by reflection.