Performance optimization of memory leak (Memory Leak) & OOM & ANR analysis

1 memory leak

1.1 What is a memory leak in Java

When an object is no longer needed and should be recycled, another object that is being used holds a reference to it, which prevents the object from being recycled by the GC. This leads to the fact that the objects that should have been recycled cannot be recycled and stay in the heap memory, resulting in a memory leak.
  In Java, a memory leak is the existence of some allocated objects, which have the following two characteristics: ① These objects are reachable , that is, there are paths in the directed graph that can be connected to gc roots (directly or indirectly referenced to gc roots); ② These objects are useless , that is, the program will not use these objects in the future. If objects meet these two conditions, these objects can be judged as memory leaks in Java. These objects will not be reclaimed by GC, but they occupy memory. Combining GC garbage collection mechanism: JVM/DVM and garbage collection mechanism for performance optimization

1.2 Causes of memory leaks in Android

If a long-lived object holds a reference to a short-lived object, causing the memory of an object that will no longer be used to be recycled, a memory leak is likely to occur, which is why memory leaks occur in Java .

1.3 Legend Analysis

In C++, the scope of memory leaks is larger. Some objects are allocated memory space, but then they are unreachable. Since there is no GC in C++, these memory will never be recovered. In Java, these unreachable objects are reclaimed by GC, so programmers don't need to consider this part of memory leaks.
  Through analysis: For C++, programmers need to manage edges and vertices by themselves; and for Java, programmers only need to manage edges, not the release of vertices. **In this way, Java improves the efficiency of programming.

Write picture description here

1.4 Impact of memory leaks

(1) Application lag : The leaked memory affects the memory allocation of the GC, and excessive memory leaks will affect the execution efficiency of the application.
(2) Memory overflow (OOM) : Excessive memory leaks will eventually lead to OOM in the memory allocated by Dalvik.

1.5 A memory leak case that is not noticed

(1) An example of a memory leak where a long-lived object holds a reference to a short-lived object

public class Simple {
    
    
	Object object;
	public void method() {
    
    
		object = new Object();
		//...其他代码
	}
}

The object instance here, we expect it to only be used in the method() method, and it will not be used by other objects. However, when the method () method is executed, the memory allocated by the object object will not be immediately considered as an object that can be released. It will be released only after the object created by the Simple class is released. Strictly speaking, this is A memory leak.
(2) Solution 1: Use object as a local variable in the method1() method (local variables are variables belonging to the method, and the life cycle ends with the method.)

public class Simple {
    
    
	public void method() {
    
    
		Object object = new Object();
		//...其他代码
	}
}

(3) Solution 2: Assign a null value to the object, and then no longer call

public class Simple {
    
    
	Object object;
	public void method1(){
    
    
		object = new Object();
		//...其他代码
		object = null;
	}
}

1.6 Summary

When the memory object is obviously no longer needed, the memory and its access method (reference) are still retained. This is a memory leak that may occur in all languages. If we are not careful when programming, we can easily happen to this situation, if it is not serious, it may be just a short-lived memory leak.

2 Scenario analysis that causes memory leaks

2.1 Memory leaks caused by static variables

The life cycle of static variables in java starts when the class is loaded and ends when the class is unloaded . In other words, its life cycle in android starts when the process starts and ends when the process dies. So during the running of the program, if the process is not killed, the static variable will always exist and will not be recycled. If the static variable strongly refers to a variable in an Activity, then the Activity will not be released , even if the Activity executes onDestroy (do not equate the execution of onDestroy with being recycled).
  The solution to this type of problem is:
  ①Look for an alternative object with a similar life cycle to the static variable ;
  ②If you can’t find it, change the strong reference to a weak reference . Typical examples are as follows:

2.1.1 Context memory leak caused by singleton

public class Singleton {
    
    
    private Context context;
    private volatile static Singleton instance = null;
    private Singleton(Context context) {
    
    
	    this.context = context;
    }
    public static Singleton getInstance(Context context) {
    
    
        // 两层判空,第一层是为了避免不必要的同步
        // 第二层是为了在null的情况下创建实例
        if (instance == null) {
    
    
            synchronized (Singleton.class) {
    
    
                if (instance == null) {
    
    
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

(1) Analysis: When calling getInstance, if the incoming context is the context of the Activity. As long as the singleton is not released, the Activity will not be released either.
(2) Solution: Pass in the context of the Application, because the life cycle of the context of the Application is longer than that of the Activity, it can be understood that the context of the Application is as long as the life cycle of the singleton, and it is most appropriate to pass it in.

private SingleTon(Context context) {
    
     
    this.context = context.getApplicationContext(); 
}

2.2 Memory leaks caused by non-static inner classes

The non-static inner class will hold a reference to the outer class. If the life cycle of the non-static inner class is longer than that of its outer class, it cannot be recycled when the outer class is destroyed, which will cause a memory leak (Static inner classes don't). The solution to this kind of problem is:
(1) Change the inner class into a static inner class ;
(2) If there is a strong reference to the property in the Activity, change the reference method of the property to a weak reference .
(3) When the business permits, when the Activity executes onDestory, end these time-consuming tasks .

2.2.1 The static variable of the outer class holds the non-static inner class

public class MainActivity extends AppCompatActivity {
    
     
    private static Test test; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
    
     
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        if (test == null) {
    
     
            test = new Test(); 
        } 
    } 

    private class Test {
    
     
	    //非静态内部类Test默认持有外部类MainActivity的引用
    } 
}

(1) Analysis: This is actually the same as the singleton principle. Since the life cycle of the static object test is consistent with the life cycle of the entire application, the non-static internal class Test holds a reference to the external class MainActivity, causing MainActivity to exit. Cannot be recycled, causing a memory leak.
(2) Solution: ① Change test to non-static, so that the life cycle of test is the same as that of MainActivity, and memory leaks are avoided. ② Change Test to a static inner class, so that test does not hold a reference to MainActivity, but generally there is no such operation.

2.3 About the memory leak caused by Handler

2.3.1 Handler or Runnable as non-static inner class

Both Handler and runnable have timer functions. When they are non-static inner classes, they will also hold references to outer classes. If there is a delay operation inside them, the outer class will be destroyed before the delay operation occurs. , then the external class object cannot be recycled, resulting in a memory leak. Suppose the code of Activity is as follows:

public class MainActivity extends AppCompatActivity {
    
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {
    
      
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        new Handler().postDelayed(new Runnable() {
    
      
            @Override  
            public void run() {
    
      

            }  
        }, 10 * 1000);  
    }  
}

(1) Analysis: Handler and Runnable, as anonymous inner classes, will hold a reference to MainActivity , and they have a 10-second timer inside. If MainActivity is closed within 10 seconds of opening MainActivity, then due to the Handler and Runnable The life cycle is longer than that of MainActivity, which will cause MainActivity to fail to be recycled , resulting in memory leaks.
(2) Solution 1: The general routine is to define Handler and Runnable as static inner classes, so that they no longer hold references to MainActivity, thereby avoiding memory leaks.

public class MainActivity extends AppCompatActivity {
    
      
    private Handler handler;  
    private Runnable runnable;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
    
      
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        handler = new TestHandler();  
        runnable = new TestRunnable();  
        handler.postDelayed(runnable, 10 * 1000);  
    }  

    private static class TestHandler extends Handler {
    
      
    }  
    private static class TestRunnable implements Runnable {
    
      
        @Override  
        public void run() {
    
      
            Log.d(TAG, "run: ");  
        }  
    }  
    private static final String TAG = "MainActivity";  
}

(3) Solution 2: Call the removeCallbacks method of the handler in onDestory to remove the Message. This will not only avoid memory leaks, but also cancel the timer when exiting the Activity, ensuring that the run method will not be executed after 10 seconds.

@Override  
protected void onDestroy() {
    
      
    super.onDestroy();  
    handler.removeCallbacks(runnable);  
}

2.3.2 Handler or Runnable holds Context object

public class MainActivity extends AppCompatActivity {
    
     
	private MyHandler handler;
    @Override 
    protected void onCreate(Bundle savedInstanceState) {
    
     
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        handler = new MyHandler(this); 
        handler.sendEmptyMessageDelayed(0, 10*1000)); 
    } 

    private static class MyHandler extends Handler {
    
    
	    Context context;
	    MyHandler(Context context) {
    
    
	        this.context = context;
	    }
	    @Override
	    public void handleMessage(Message msg) {
    
    
	        if (context != null) {
    
    
	            mImageView.setImageBitmap(mBitmap);
	        }
	    }
	}
}

(1) Analysis: Since the Context object is held in the Handler, and this Context object is passed in through the construction method of TestHandler, it is a MainActivity object. That is to say, although MyHandler, as a static inner class, does not hold a reference to the external class MainActivity , we pass in the MainActivity object when calling its constructor, so that the handler object holds a reference to MainActivity. If the user is at 10 The Activity is closed within seconds, and this handler is executed after 10 seconds. It holds a reference to the Activity, which makes the Activity unable to be recycled.
(2) Solution: Use weak references to refer to Context to avoid memory leaks .

public class MainActivity extends AppCompatActivity {
    
     
    private MyHandler handler;
   
    @Override 
    protected void onCreate(Bundle savedInstanceState) {
    
     
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        handler = new MyHandler(this); 
        handler.sendEmptyMessage(1);
    } 

    private class MyHandler extends Handler {
    
    
	    WeakReference<MainActivity> mActivity;
	    MyHandler(MainActivity activity) {
    
    
	        mActivity= new WeakReference<MainActivity>(activity);
	    }
	    
	    @Override
	    public void handleMessage(Message msg) {
    
    
		   final MainActivity activity = mActivity.get();
		   if (ActivityUtils.isActivityFinished(activity)) {
    
    
				return;
		   }
	        
		   mImageView.setImageBitmap(mBitmap);
	    }
	}
} 

2.3.3 Summary understanding about Handler Leak

(1) A common example of Handler

Handler handler = new Handler() {
    
    
        public void handleMessage(Message msg) {
    
    
            switch (msg.what) {
    
    
                case HyyConstants.REFRESH_LIST:
                    Toast.makeText(getApplicationContext(), "Refresh list", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

(2) Analysis: The Message objects queued in the MessageQueue of the application thread still retain their target Handler. If Handler is an inner class (note: whether it is anonymous or non-anonymous, anonymous is a more common usage), its outer class will be reserved (as for why, please refer to the relevant description of Java nested classes). In order to avoid leaking the external class, declare a Handler subclass as a static internal class (note: this avoids the automatic reference of the Handler object to the external class instance), which holds a WeakReference to the external class object inside.

(3) Let’s analyze these leakage problems again:
①The holding of the Message object in the queue to the Handler leads to leakage;
②The Handler object holds a strong reference to the instance of the external class (such as Activity or Service).

(4) Solution:
① For the first reason, clear the Message object sent to the Handler in the MessageQueue before the life cycle of the component using the Handler ends (for example, call the remove* method of the Handler in the onDestroy() of the Activity or Service ).
②For the second reason, the implementation class of Handler adopts the method of static inner class to avoid strong reference to the outer class, and declares a WeakReference inside it to refer to the instance of the outer class.

(5) About the remove() method of Handler (you can refer to the source code or documentation)
①removeCallbacks(Runnable r) ——Clear the Message on r matching.
②removeCallbacks(Runnable r, Object token) ——Clear the Message that r matches and matches Message.obj. When the token is empty, only match r.
③removeCallbacksAndMessages(Object token) ——Clear the Message on the token match.
④removeMessages(int what)——Match by what
⑤removeMessages(int what, Object object)——Match by what
⑥handler.removeCallbacksAndMessages(null); I——clear all Messages (including Callback) that target the Handler,

(6) The best solution

public class MyActivity extendsActivity {
    
    
    private MyHandler mHandler;
    @Override
    protectedvoid onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        mHandler = newMyHandler(this);
    }
 
    @Override
    protected void onDestroy() {
    
    
        mHandler.removeCallbacksAndMessages(null);   // Remove all Runnable and Message.
        super.onDestroy();
    }
 
   private static class MyHandler extends Handler {
    
    
        private WeakReference<MyActivity> mActivity;    // WeakReference to the outer class's instance.
 
        public MyHandler(MyActivity activity) {
    
    
            mActivity= new WeakReference<MyActivity>(activity);
        }
 
        @Override
        public void handleMessage(Message msg) {
    
    
            final MyActivity activity = mActivity.get();
            if (ActivityUtils.isActivityFinished(activity)) {
    
    
                return;
            }
            
            if(outer != null) {
    
    
                // Do something with outer as your wish.
            }
        }
    }
}

 /**
     * 直接判断Activity是否已经销毁
     *
     * @param activity
     * @return true=Activity已经销毁,false=没有销毁
     */
    public static boolean isActivityFinished(Activity activity) {
    
    
        return activity == null || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) || activity.isFinishing();
    }

(7) Reference link
for a little understanding of HandlerLeak

2.3 Memory leaks caused by unclosed resources

When resources such as BroadcastReceiver, Cursor, Bitmap, and custom attribute attr are used, they need to be released in time when they are not needed. If they are not released, memory leaks will occur

2.4 Infinite loop animation

Play a type of infinite loop animation in the property animation in the Activity, without stopping the animation in the ondestory, the Activity will be held by the animation and cannot be released

2.5 Memory leaks when using containers

Vector vector = new Vector(10); 
for (int i = 1; i < 100; i++) {
    
     
    Object obj = new Object(); 
    vector.add(obj); 
    obj = null;  
}

In this example, we circularly apply for the Object object, put the applied object into the Vector, and only release the reference itself, then the Vector still refers to the object , so this object is not recyclable for GC. And the memory leak here may be short-lived, because those objects can still be recycled after the entire method() method is executed (but we should also avoid it).
  Therefore, if an object must be deleted from the Vector after it has been added to the Vector, the easiest way is to set the Vector object to null.

//...对vector的操作
vector = null;

3 Out of Memory (OOM)

3.1 Causes of OOM

When the heap resources of the application exceed the memory allocated by the Dalvik virtual machine (64M/48M/24M), memory overflow occurs.

3.2 What causes OOM

  • More memory leaks can easily lead to OOM.
  • Processing of large images. When compressing pictures, we must pay attention to the frequent creation and recycling of objects during normal development.
  • Can be properly detected: ActivityManager.getMemoryClass() can be used to query the HeapSize threshold of the current application. It can be viewed by naming adb shellgetProp | grep dalvik.vm.heapxxxlimit.

3.3 How to avoid OOM

3.3.1 Reduce the memory footprint of objects

(1) Use a more lightweight data structure . It is more efficient to use Android's data structure classes ArrayMap/SparseArray, SparseLongMap/SparseIntMap specially developed for mobile phones instead of traditional data structures such as HashMap under appropriate circumstances. HashMap.put(string,Object);Object o = map.get(string), will cause some unnecessary automatic boxing and unboxing.
(2) Reduce the memory usage . Resource images are compressed . Use inSampleSize image compression ratio for image compression , which can avoid OOM caused by large image loading; decodeformat: selection of image decoding format, ARGB_8888/ RGB_565 /ARGB_4444/ALPHA_8, WebP can also be used.
(3) Properly avoid using Enum enumerations in android, instead of using ordinary static constants. (Generally, multi-use enumeration is still advocated—in terms of software architecture design; if you encounter this enumeration that needs to be used extensively, you should be more inclined to solve performance problems.)

3.3.2 Reuse of memory objects

(1) ListView/GridView source code can see the reuse of ConvertView. Recycler source code in RecyclerView.
(2) Multiplexing of Bitmap : To display a large number of pictures such as Listview, it is necessary to use the LRU cache mechanism to multiplex the pictures.
(3) Avoid creating objects in the onDraw method, and reuse them to avoid memory jitter.
(4) Appropriate classes are used for string operations on large amounts of data : StringBuilder is used for single-threaded operation of large amounts of data under the string buffer; StringBuffer is used for multi-threaded operation of large amounts of data under the string buffer; String is used for operation of small amounts of data.

3.3.3 Avoid object memory leaks

Reference -- 2 Scenario analysis that causes memory leaks

3.3.4 Optimization strategy to use some memory

4 ANR positioning and correction

4.1 Positioning

(1) Use the logs printed in loop() to detect UI freezes in the application; Android UI performance optimization detects UI freezes in the application
(2) Track the ANR information through Bugly statistics;
(3) View /data /anr/traces.txt View ANR information.

4.2 Root cause

The main thread freezes, causing the application to not respond to user input events within 5 seconds.

4.3 Scenarios where ANR errors occur

(1) Try to avoid time-consuming operations in the onCreate and onResume callbacks of the Activity .
(2) IO/network operations are performed in the main thread, which is easy to block ; time-consuming calculations are performed----this is often done in the onDraw method when customizing controls (creating objects in onDraw can easily cause memory jitter-drawing actions will A large number of continuous calls, resulting in a large number of garbage objects, resulting in frequent GC and memory jitter).
(3) The BroadCastReceiver did not complete the processing within 10 seconds . The onReceived code should minimize time-consuming operations. It is recommended to use IntentService for processing.
(4) Service executes time-consuming operations , because service is also executed in the main thread, so time-consuming operations should be performed in sub-threads in service.
(5) When using Thread or HandlerThread, use Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) or java.lang.Thread.setPriority (int priority) to set the priority as the background priority, so that other multi-threads can consume CPU time concurrently It will be reduced, which is conducive to the processing of the main thread.

6 usage scenarios

6.1 The memory leak we discuss is mainly about "heap memory", which stores: the object entity pointed to by the reference.

Sometimes there is indeed a situation: it can be accessed when it is needed, it can be recycled when it is not needed, and it can be temporarily saved for reuse.
  For example: When ListView, GridView, or RecyclerView loads a large amount of data or pictures, the pictures take up a lot of memory. You must manage the memory well, otherwise it is easy to overflow the memory. The pictures that slide out are recycled to save memory. Look at the source code of ListView----recycle objects, and convert ConvertView will be reused. If the user repeatedly slides or there are the same pictures below, it will cause multiple repeated IOs (very time-consuming), then caching is needed-balance the memory size and IO, algorithms and some special java classes.

算法:lrucache(最近最少使用先回收)
特殊的java类利于回收,StrongReference,SoftReference,WeakReference,PhatomReference

Write picture description here
  Soft references are more capricious than the LRU algorithm, and the amount of recycling is relatively large, and you cannot control which objects are recycled.
  For example, usage scenarios: default avatar, default icon, ListView or GridView , and REcyclerView should use memory cache + external cache (SD card). , You can try to use soft references (SoftReference) and weak references (WeakReference).

7 Reference Links

Cause analysis and solution of memory leak in Android

Take you to thoroughly understand Android memory leaks in all directions | Case Analysis

Teach you how to analyze memory leaks on Android Studio 3.0

Summary of Android memory leaks

Reprinted: https://blog.csdn.net/chenliguan/article/details/53150793

Guess you like

Origin blog.csdn.net/gqg_guan/article/details/130708799