Android memory optimization and practical solution to memory jitter

Problem background

Suppose we have an application whose function is to display a counter on a TextView and update the counter value every second. In order to achieve this function, we use a Handler to send an empty message, update the counter value when the message is received, and send the empty message again, forming a loop. At the same time, in order to simulate some complex business logic, we created a large number of array objects in the loop. Here is our code:

public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,创建大量的数组对象
            for (int i = 0; i < 1000; i++) {
                int[] array = new int[1000];
                array[0] = i;
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}

There seems to be no problem with this code, but when we run the application, we will find that the memory usage of the application is very unstable, the memory curve is obviously jagged, and GC events are also very frequent. This is a typical phenomenon of memory jitter.

identify the problem

To locate this problem, we can use Memory Profiler to observe the memory usage of the application. Memory Profiler is a tool in Android Studio that can display the memory usage of the application in real time, including memory allocation, recycling, leaks, etc. Through Memory Profiler, we can find that the application has obvious memory fluctuations and GC frequency.

The following is an example graph of using Memory Profiler to observe application memory usage:

As can be seen from the example diagram:

  • Memory Usage : This chart shows the memory usage of the application at different points in time, as well as different types of memory (such as Java Heap, Native Heap, Graphics, etc.). As can be seen from the chart, the application's Java Heap memory usage shows obvious fluctuations, rising and falling, forming a zigzag pattern. This shows that the application has a large number of short-lived objects, resulting in imbalanced memory allocation and recycling.
  • GC Events : This chart shows the GC events that occurred in the application at different points in time, as well as the types of GC events (such as GC Alloc, GC Concurrent, etc.). As can be seen from the chart, the application has very frequent GC events, with a GC event occurring almost every second. This shows that the GC pressure of the application is very high. GC needs to consume more CPU resources and affect the execution of application threads.

Re-output from here, and I will continue to introduce you to the practical solution to memory jitter. Next, I will show you how to use Memory Profiler and code troubleshooting to analyze and solve a specific memory jitter problem from the following aspects:

problem analysis

To analyze this problem, we can use Memory Profiler to capture a heap dump and use object allocation traces to find out where and why the code is causing memory thrashing. A heap dump is a file that saves a memory snapshot of an application at a certain point in time. It can be used to view all objects that exist in the application, as well as their type, size, number, etc. Object allocation tracking is a feature that records all objects created by an application over a period of time, along with their type, size, number, call stack, etc. Through heap dumps and object allocation tracing, we can discover the code locations and causes of memory thrashing in the application.

Here is an example plot of using Memory Profiler to capture a heap dump and object allocation trace:

As can be seen from the example diagram:

  • Heap Dump : This function allows us to capture the heap dump file of the application at a certain point in time and view its contents. From the heap dump file, we can see all the classes and instances present in the application, along with their type, size, number, etc. By comparing heap dump files at different points in time, we can discover which classes and instances are short-lived and cause memory fluctuations.
  • Allocation Tracking : This feature allows us to record all objects created by the application over a period of time and view their contents. From the object allocation trace, we can see all the objects created in the application, as well as their type, size, quantity, call stack, etc. By analyzing the object allocation trace, we can find out which code locations create a large number of objects, causing frequent GC.

Using these two functions, we can locate the code location and cause of memory thrashing. After analysis, we found:

  • In the heap dump file, we found that there are a large number of int[] array objects existing in the Java Heap, which occupy most of the memory space, and in the heap dump file at different points in time, their number and size are different. There are obvious changes. This shows that these array objects are short-lived, causing memory fluctuations.
  • In the object allocation tracking, we found that a large number of int[] array objects were created, and their types, sizes, and quantities were consistent. By looking at their call stacks, we find that they are all created in the handleMessage method of MainActivity. This shows that this method creates a large number of array objects, causing frequent GC. Next, I will show you how to use Memory Profiler and code troubleshooting to analyze and solve a specific memory jitter problem from the following aspects:

problem solved

To solve this problem, we can modify the code logic to avoid creating a large number of arrays in the loop, or use an object pool to reuse array objects, thereby reducing memory allocation and recycling. The following is our optimization plan:

  • Avoid creating a large number of arrays in a loop : If we do not need to create a large number of arrays in a loop, we can place the creation of the array outside the loop, or use static variables or member variables to save the array. This avoids creating a new array object each time through the loop and reduces memory allocation and recycling. For example:
public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,使用一个静态变量来保存数组对象,避免在循环中创建大量数组对象
            for (int i = 0; i < 1000; i++) {
                array[0] = i;
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };
    // 定义一个静态变量,用来保存数组对象
    private static int[] array = new int[1000];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}
  • Use object pool to reuse array objects : If we need to create a large number of arrays in a loop, we can use object pool to manage and reuse array objects instead of creating and destroying array objects every time. When an array is needed, a free array can be obtained from the object pool. After use, the array can be returned to the object pool and wait for the next use. This can avoid frequent memory allocation and recycling and reduce GC pressure. For example:
public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,使用一个对象池来管理和复用数组对象,避免在循环中创建大量数组对象
            for (int i = 0; i < 1000; i++) {
                // 从对象池中获取一个空闲的数组对象
                int[] array = arrayPool.getArray();
                array[0] = i;
                // 将数组对象归还到对象池中
                arrayPool.returnArray(array);
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };
    // 定义一个对象池,用来管理和复用数组对象
    private ArrayPool arrayPool;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 初始化对象池
        arrayPool = new ArrayPool(1000, 1000);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}

In order to help everyone understand performance optimization more comprehensively and clearly, we have prepared relevant core notes (including underlying logic):https://qr18.cn/FVlo89

Core notes on performance optimization:https://qr18.cn/FVlo89

Startup optimization

, memory optimization,

UI optimization,

network optimization,

Bitmap optimization and image compression optimization : multi-thread concurrency optimization and data transmission efficiency optimization, volume package optimizationhttps://qr18.cn/FVlo89




"Android Performance Monitoring Framework":https://qr18.cn/FVlo89

"Android Framework Learning Manual":https://qr18.cn/AQpN4J

  1. Boot Init process
  2. Start the Zygote process on boot
  3. Start the SystemServer process on boot
  4. Binder driver
  5. AMS startup process
  6. PMS startup process
  7. Launcher startup process
  8. Android four major components
  9. Android system service-Input event distribution process
  10. Android underlying rendering - screen refresh mechanism source code analysis
  11. Android source code analysis in practice

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/133245881