Android performance optimization: thoroughly understand the principles and optimization of memory leaks from the perspective of freeze and ANR

Android performance optimization: thoroughly understand the principles and optimization of memory leaks from the perspective of freeze and ANR

[[415143]]

Preface

  • JAVA programs should not have memory leaks because of the garbage collection mechanism. We already know that if an object is reachable from the root node, that is, there is a reference chain from the root node to the object, then the object will not be recycled by GC.
  • If this object is no longer used and is useless, but we still hold its reference, it will cause a memory leak. For example, a thread running in the background for a long time holds a reference to an Activity. At this time, the Activity executes the onDestroy method. Then this Activity is a useless object that can be reached from the root node. This Activity object is a leaked object, and the memory allocated to this object cannot be recycled.
  • If our Java runs for a long time and this kind of memory leak continues to occur, there will be no memory available in the end.
  • Of course, Java memory leaks are different from C/C++ memory leaks. If a Java program is completely terminated, all its objects will be unreachable, and the system can perform garbage collection on them. Its memory leak is limited to itself and will not affect the entire system. C/C++ memory leaks are worse. Its memory leaks are system-level. Even if the C/C++ program exits, its leaked memory cannot be recycled by the system and will never be usable unless the machine is restarted.
  • This article begins to summarize Android memory leaks;

1. Introduction to Android memory leak

1. What is a memory leak?

  • Memory leak refers to the situation where the dynamically allocated heap memory in the program is not released or cannot be released by the program for some reason, resulting in a waste of system memory, slowing down the program running speed or even causing serious consequences such as system crash.
  • Memory leaks are hidden and cumulative, making them more difficult to detect than other memory access errors. Because memory leaks are caused by memory blocks not being released, they are omission-type defects rather than fault-type defects. In addition, memory leaks usually do not directly produce observable error symptoms, but gradually accumulate, reducing the overall performance of the system, and in extreme cases may cause the system to crash;
  • A memory leak in one Android application has little impact on other applications. In order to enable Android applications to run safely and quickly, each Android application uses a dedicated Dalvik virtual machine instance to run, which is hatched by the Zygote service process, that is, each application runs in its own process.
  • Android allocates different memory usage limits for different types of processes. If a memory leak occurs during program execution and the memory used by the application process exceeds this limit, the system will regard it as a memory leak and kill it. This means that only the application's own process is killed without affecting other processes (if there is a problem with system processes such as system_process, it will cause the system to restart).

2. The harm of memory leak

  • Users may not be aware of a single memory leak, but when the leaks accumulate to the point where all memory is consumed, it can cause lag or even crashes.
  • Frequent GC recovery causes application lag ANR:
  • When the memory is insufficient, GC will actively recycle the useless memory. However, memory recycling also takes time.
  • The high frequency alternation between memory recycling and GC recycling of garbage resources will cause memory jitter.
  • A lot of data will pollute the memory heap, and many GCs will be started immediately. Due to this additional memory pressure, there will also be a sudden increase in operations causing jamming.
  • Any operation of any thread will need to be paused and wait for the GC operation to complete before other operations can continue to run, so the fewer times the garbage collection runs, the less impact it has on performance;

3. Causes of memory leaks

① If the memory space is not recycled after use, it will cause memory leaks. Although Java has a garbage collection mechanism, there are still many code logics in Java that cause memory leaks. The garbage collector will recycle most of the memory space, but some memory space still retains references, but logically no longer used objects. At this time, the garbage collector is powerless and cannot recycle them, such as:

  • Forgetting to free allocated memory;
  • The application no longer needs this object, but has not released the reference to this object;
  • The garbage collector cannot recycle the object held by the strong reference;
  • The life cycle of the held object is too long, resulting in the inability to recycle;

②Memory leaks on the Android (Java) platform refer to the fact that useless object resources maintain a reachable path with GC Roots, which makes the system unable to recycle;

③Then the objects popped from the stack will not be treated as garbage collection. Even if the program no longer uses these objects in the stack, they will not be recycled because the stack still holds references to these objects, commonly known as expired references. This memory leak is very hidden.

2. Detection of memory leak detection tools

①Memory Monitor

Located in Android Monitor, this tool can:

  • Conveniently display memory usage and GC status
  • Quickly determine whether the jam is related to GC
  • Quickly locate whether the crash is related to excessive memory usage
  • Quickly locate potential memory leaks (memory usage keeps growing)
  • But the problem cannot be accurately located

②Allocation Tracker

This tool is used to:

  • You can locate the object type, size, time, thread, stack, and other information allocated in the code
  • Can locate memory jitter problems
  • Use Heap Viewer to locate memory leaks (you can find out where the leaked object was created, etc.)
  • How to use: There is a Start Allocation Tracking button in Memory Monitor to start tracking. After clicking Stop Tracking, the statistical results will be displayed.

③Heap Viewer

This tool is used to:

  • Display memory snapshot information
  • Collect information once after each GC
  • A powerful tool for finding memory leaks
  • How to use: There is a Dump Java Heap button in Memory Monitor, just click it, and select the package category in the upper left corner of the statistical report. Use the initiate GC button in Memory Monitor to detect memory leaks and other situations.

④LeakCanary

  1. dependencies {
  2. debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'  
  3. releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'  
  4. // Optional, if you use support library fragments:
  5. debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'  
  6. }

Use it directly in the Application, then run the APP and it will automatically detect it. If it is detected, it will notify you on another APP and display the details.

  1. public class ExampleApplication extends Application {
  2. @Override public void onCreate() {
  3. super.onCreate();
  4. if (LeakCanary.isInAnalyzerProcess(this)) {
  5. // This process is dedicated to LeakCanary for heap analysis.
  6. // You should not init your app in this process.
  7. return ;
  8. }
  9. LeakCanary.install(this);
  10. //Normal app init code...
  11. }
  12. }

3. Detailed explanation of common memory leak scenarios

1. Singleton causes memory leak

The singleton pattern is often used in Android development, but if used improperly, it can lead to memory leaks. Because the static nature of the singleton makes its life cycle as long as the life cycle of the application, if an object is no longer useful, but the singleton still holds its reference, it cannot be properly recycled during the entire life cycle of the application, resulting in memory leaks.

  1. public class AppSettings {
  2. private static volatile AppSettings singleton;
  3. private Context mContext;
  4. private AppSettings(Context context) {
  5. this.mContext = context;
  6. }
  7. public   static AppSettings getInstance(Context context) {
  8. if (singleton == null ) {
  9. synchronized (AppSettings.class) {
  10. if (singleton == null ) {
  11. singleton = new AppSettings(context);
  12. }
  13. }
  14. }
  15. return singleton;
  16. }
  17. }

For singletons like the one in the code above, if we pass in a context parameter such as Activity or Service when calling the getInstance(Context context) method, memory leaks will occur. Take Activity as an example. When we start an Activity and call the getInstance(Context context) method to get the AppSettings singleton, passing in Activity.this as context, the AppSettings singleton sInstance holds a reference to the Activity. When we exit the Activity, the Activity is useless. However, because sIntance is a static singleton (which exists throughout the life cycle of the application), it will continue to hold a reference to the Activity, causing the Activity object to be unable to be recycled and released, which causes a memory leak.

To avoid memory leaks caused by such singletons, we can change the context parameter to a global context:

  1. private AppSettings(Context context) {
  2. this.mContext = context.getApplicationContext();
  3. }

2. Static variables cause memory leaks

Static variables are stored in the method area, and their life cycle starts from class loading and ends at the end of the entire process. Once a static variable is initialized, the reference it holds will not be released until the process ends. For example, in the following situation, in order to avoid repeated creation of info in Activity, sInfo is used as a static variable:

  1. public class MainActivity2 extends AppCompatActivity {
  2. public   static Info sInfo;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. sInfo = new Info(this);
  7. }
  8. class Info {
  9. private Context mContext;
  10. public Info(Context context) {
  11. this.mContext = context;
  12. }
  13. }
  14. }

Info is a static member of Activity and holds a reference to Activity, but sInfo is a static variable and its life cycle is definitely longer than Activity. So when Activity exits, sInfo still references Activity and Activity cannot be recycled, which leads to memory leaks.

In Android development, static holding may often cause memory leaks due to inconsistent life cycles, so when creating new static holding variables, we need to consider the reference relationship between members and use static holding variables as little as possible to avoid memory leaks. Of course, we can also reset the static variable to null at an appropriate time so that it no longer holds a reference, which can also avoid memory leaks.

3. Non-static inner classes cause memory leaks

Non-static inner classes (including anonymous inner classes) hold references to outer classes by default. When the lifecycle of a non-static inner class object is longer than that of an outer class object, memory leaks will occur. A typical scenario for memory leaks caused by non-static inner classes in Android development is the use of Handlers. Many developers write Handlers like this:

  1. public class MainActivity2 extends AppCompatActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. start();
  6. }
  7. private void start() {
  8. Message message = Message.obtain();
  9. message.what = 1;
  10. mHandler.sendMessage(message);
  11. }
  12. private Handler mHandler = new Handler() {
  13. @Override
  14. public void handleMessage(Message msg) {
  15. super.handleMessage(msg);
  16. if (msg.what == 1) {
  17. //doNothing
  18. }
  19. }
  20. };
  21. }

Some people may say that mHandler does not hold the reference of Activity as a static variable, and its life cycle may not be longer than that of Activity, so it should not necessarily cause memory leaks. Obviously, this is not the case! Those who are familiar with the Handler message mechanism know that mHandler will be saved as a member variable in the sent message msg, that is, msg holds the reference of mHandler, and mHandler is a non-static inner class instance of Activity, that is, mHandler holds the reference of Activity, so we can understand that msg indirectly holds the reference of Activity. After msg is sent, it is first placed in the message queue MessageQueue, and then waits for Looper's polling processing (MessageQueue and Looper are both associated with threads, MessageQueue is a member variable referenced by Looper, and Looper is stored in ThreadLocal). Then when the Activity exits, msg may still exist in the message queue MessageQueue and is not processed or being processed, which will cause the Activity to be unable to be recycled, resulting in a memory leak in the Activity.

Usually in Android development, if you want to use inner classes but want to avoid memory leaks, you usually use static inner classes + weak references.

  1. MyHandler mHandler;
  2. public   static class MyHandler extends Handler {
  3. private WeakReference<Activity> mActivityWeakReference;
  4. public MyHandler(Activity activity) {
  5. mActivityWeakReference = new WeakReference<>(activity);
  6. }
  7. @Override
  8. public void handleMessage(Message msg) {
  9. super.handleMessage(msg);
  10. }
  11. }

mHandler holds the Activity through weak references. When GC performs garbage collection, it will recycle and release the memory unit occupied by the Activity. In this way, memory leaks will not occur. The above approach does avoid memory leaks caused by the Activity. The sent msg no longer holds the reference to the Activity, but the msg may still exist in the message queue MessageQueue, so it is better to remove the callback of mHandler and the sent message when the Activity is destroyed.

  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. mHandler.removeCallbacksAndMessages( null );
  5. }

Another situation where non-static inner classes cause memory leaks is when using Thread or AsyncTask. To avoid memory leaks, you still need to use static inner classes + weak applications like the Handler above (the code is not listed, refer to the correct writing method of Handler above).

4. Failure to cancel registration or callback causes memory leak

For example, if we register a broadcast in an Activity and do not cancel the registration after the Activity is destroyed, the broadcast will always exist in the system, holding an Activity reference like the non-static inner class mentioned above, causing a memory leak. Therefore, after registering a broadcast, you must cancel the registration after the Activity is destroyed. When registering an observer mode, memory leaks can also occur if the registration is not canceled in time. For example, when using Retrofit+RxJava to register an observer callback for a network request, it also holds an external reference as an anonymous inner class, so you need to remember to cancel the registration when it is not used or destroyed.

5. Timer and TimerTask cause memory leaks

Timer and TimerTask are usually used in Android to perform some timing or cyclic tasks, such as implementing an infinitely rotating ViewPager:

  1. private void stopTimer(){
  2. if(mTimer!= null ){
  3. mTimer.cancel();
  4. mTimer.purge();
  5. mTimer = null ;
  6. }
  7. if(mTimerTask!= null ){
  8. mTimerTask.cancel();
  9. mTimerTask = null ;
  10. }
  11. }
  12. @Override
  13. protected void onDestroy() {
  14. super.onDestroy();
  15. stopTimer();
  16. }

When our Activity is destroyed, it is possible that the Timer is still waiting to execute the TimerTask. The reference it holds to the Activity cannot be recycled. Therefore, when our Activity is destroyed, we must immediately cancel the Timer and TimerTask to avoid memory leaks.

6. Objects in the collection are not cleaned up, causing memory leaks

This is easy to understand. If an object is put into a collection such as ArrayList or HashMap, the collection will hold a reference to the object. When we no longer need the object, we do not remove it from the collection. As long as the collection is still in use (and the object is no longer useful), the object will cause a memory leak. And if the collection is referenced statically, the useless objects in the collection will cause a memory leak. Therefore, when using a collection, you should remove unused objects from the collection in a timely manner, or clear the collection, to avoid memory leaks.

7. Memory leaks caused by resources not being closed or released

When using IO, File stream, Sqlite, Cursor and other resources, they should be closed in time. These resources usually use buffers when performing read and write operations. If they are not closed in time, these buffer objects will be occupied and not released, resulting in memory leaks. Therefore, we should close them in time when they are not needed so that the buffers can be released in time to avoid memory leaks.

8. Property animation causes memory leaks

Animation is also a time-consuming task. For example, if you start an attribute animation (ObjectAnimator) in an Activity, but do not call the cancel method when destroying it, we will not see the animation, but the animation will continue to play. The animation references the control, and the control references the Activity, which will cause the Activity to fail to be released normally. Therefore, you should also cancel the attribute animation when destroying the Activity to avoid memory leaks.

9. WebView causes memory leak

Regarding the memory leak of WebView, since WebView will occupy memory for a long time after loading a web page and cannot be released, we need to call its destory() method to destroy it and release memory after the Activity is destroyed. In addition, when looking up the relevant information about WebView memory leak, I saw this situation: the Callback under Webview holds the Activity reference, causing the Webview memory to be unable to be released, and even calling methods such as Webview.destory() cannot solve the problem (after Android 5.1). The final solution is: before destroying WebView, you need to remove WebView from the parent container first, and then destroy WebView.

Summarize

  • For objects (singleton) whose lifecycle is longer than that of an Activity, avoid directly referencing the Activity context. Consider using ApplicationContext and emptying static variables when they are not in use.
  • It is best to use weak references for the references held by the Handler. When the Activity is released, remember to clear the Message and cancel the Runnable of the Handler object;
  • Non-static inner classes and non-static anonymous inner classes will automatically hold references to outer classes. To avoid memory leaks, you can consider declaring inner classes as static.
  • When using broadcast receivers, EventBus, etc., registration/unregistration should be used in pairs, and all registrations should have unregistrations;
  • Remember to properly close resource objects such as Cursor, File, Bitmap, etc. that are no longer in use;

When something is added to a collection, there should be a corresponding deletion.

Cancel attribute animation in time, pay attention to webview memory leak problem

<<:  Google Chrome for Android gets two-factor authentication

>>:  How to choose a mobile phone? I'll share my experience of changing phones over the years.

Recommend

How much does mutton cost per pound? What is the current market price of mutton?

With the arrival of autumn, the demand for mutton...

Playing with photography HTC is pushing itself into the abyss of darkness

Is it time? Is it fate? Often, the development of...

What can save you? ——Top 10 endangered species (1)

Translator's note: The International Departme...

Curious questions for the Year of the Snake! Explore the most "snake"

Review expert: Wang Lei, National Park and Nature...

Analysis of Xianyu's competitive products

The demand for second-hand goods market is growin...

Analyzing the 4 ways to use Douyin to promote accounts!

What is a Douyin account? A Douyin account that i...