Android performance optimization--memory optimization analysis summary

1. Memory optimization concept

1.1 Why should memory optimization be done?

Memory optimization has always been a very important point but lacks attention. As one of the most important resources for program running, memory needs to be reasonably allocated and recycled during the running process. Unreasonable memory usage can even cause the user's application to run. Stuttering, ANR, and black screen may even cause OOM (out of memory) crashes in user applications. If you follow it carefully, you may find that the place where the memory problem occurs is just a manifestation, not the deep-seated cause, because the memory problem is relatively complex. It is a gradual squeezing process, and it happens to be in the code where you have the problem. Exploded, so developers must pay more attention to application memory problems.

1.2 The manifestation of memory problems

  • Memory jitter: sawtooth, GC frequent cause freeze

  • Memory leak: available memory gradually decreases, frequent GC

  • Memory overflow: OOM, program exception

2. Commonly used memory analysis tools

To solve memory problems, we must have powerful memory analysis tools that allow us to locate memory problems faster and more conveniently. The current mainstream memory analysis tools mainly include LeakCanary, Memory Profiler, and MAT.

2.1 LeakCanary

LeakCanary is an open source memory leak monitoring framework from Square. Memory leaks that occur while the application is running will be monitored and recorded by LeakCanary.

 LeakCanary memory leak trace analysis, mainly looking at the trace from Leaking: NO to Leaking: YES, you can find that TextView has a memory leak because it holds the context of the destroyed Activity.

For more specific trace analysis, you can view the official document Fixing a memory leak.

Although using LeakCanary is very convenient, it also has certain disadvantages:

  • Directly referencing the LeakCanary used by dependencies is generally used for offline debugging and needs to be closed when the application is released online.

  • Application debugging sometimes causes lags

Therefore, generally using LeakCanary is just a simple way to locate memory leaks. However, if you need better memory optimization, such as locating memory jitter, Bitmap optimization, etc., you still need other analysis tools. The main commonly used ones are Memory Profiler and MAT.

2.2 NativeSize、Shallow Size、Retained Size、Depth

When describing Memory Profiler and MAT later, several more important indicators will often appear: Shallow Size and Retained Size. Native Size and Depth are also provided in Memory Profiler.

After you get a Heap Dump, Memory Profiler will display a list of classes. For each class, the Allocations column shows the number of instances. To the right of it are Native Size, Shallow Size and Retained Size:

We use the following figure to represent the application memory status recorded by a certain Heap Dump. Pay attention to the red node. In this example, the object represented by this node refers to the Native object from our project; this situation is less common, but after Android 8.0, such a situation may occur when using Bitmap, because Bitmap Pixel information will be stored in native memory to reduce JVM memory pressure.

Let’s start with Shallow Size. This column of data is actually very simple. It is the memory size consumed by the object itself.

Depth is the shortest path from the GC Root to this instance. The numbers in the figure are the depth (Depth) of each object.

The closer an object is to the GC Root, the more likely it is to be connected to the GC Root through multiple paths, and the more likely it is to be saved during garbage collection.

Taking the red node as an example, if any reference from the left side of it is destroyed, the red node will become inaccessible and be garbage collected. For the blue node on the right, if you want it to be garbage collected, you need to destroy the paths on the left and right.

It is worth noting that if you see an instance with a Depth of 1, it means that it is directly referenced by the GC Root, which also means that it will never be automatically recycled.

Below is a sample Activity that implements the LocationListener interface. The highlighted part of the code, requestLocationUpdates(), will use the current Activity instance to register the locationManager. If you forget to log out, this activity will leak. It will stay in memory forever, because the location manager is a GC Root and will always exist:

You can view this in Memory Profiler. Click on an instance and the Memory Profiler will open a panel showing who is referencing the instance:

We can see that the mListener in the location manager is referencing this Activity. You can go a step further and navigate to the reference view of the heap via the References panel, which will allow you to verify that the reference chain is what you expect, and also help you understand if and where there are leaks in your code.

2.3 Memory Profiler

Memory Profiler is a memory analysis tool built into Android Studio suitable for viewing real-time memory conditions.

2.3.1 Memory Profiler interface description

Official documentation: Use Memory Profiler to view Java heap and memory allocation

2.3.2 Memory Profiler to find memory jitter

Finding memory jitter is relatively simple. The running program will appear in Memory Profiler as memory fluctuates up and down in a short period of time, triggering GC recycling frequently.

Common places where memory jitter occurs:

  • Customize View's onMeasure(), onLayout(), onDraw() to create objects directly with new

  • Lists such as RecyclerView's onBindViewHolder() directly create objects with new

  • Creating objects in code with loops

Use a simple case to simulate memory thrashing:

publicclassMainActivityextendsAppCompatActivity {
​@SuppressWarnings("HandlerLeak")privateHandler mHandler = newHandler() {@OverridepublicvoidhandleMessage(Message msg) {// 模拟内存抖动for (int i = 0; i < 100; i++) {String[] args = newString[100000];}
​mHandler.sendEmptyMessageDelayed(0, 30);}};
​@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
​findViewById(R.id.button).setOnClickListener(newView.OnClickListener() {@OverridepublicvoidonClick(View v) {mHandler.sendEmptyMessage(0);}});}
}
复制代码

The case is very simple, that is, objects are frequently created when clicking buttons. Running the above program on a real machine may not cause jagged memory fluctuations, but there will be very frequent GC recycling, as shown below:

So how should we specifically locate where the memory jitter occurs?

Follow the steps above:

  • Position ①: When the program is running, click the Record button to record the memory status, and then click Stop to stop recording. The above picture will be displayed.

  • Position ②: We can click Allocations to view the number of allocated objects in descending order from large to small or in ascending order from small to large. Generally, we will choose to view the largest number of objects in descending order from large to small. The largest number of objects in the above figure is the String object

  • Position ③: Select a String object in the Instance View and the Allocation Call Stack below will be displayed. It will display the call stack location of this object.

  • Position ④: From the Allocation Call Stack, we can see that the String object is created in handleMessage() on line 18 of MainActivity, thus locating the location of memory jitter.

There are some tips for the above operation:

  • Before operating in position ①, in order to eliminate interference, the changed memory will usually be manually GCed before recording; on devices above Android 8.0, you can drag the Memory Profiler in real time to select the memory fluctuation range to view.

  • Position ② The above example is to directly view Arrange by class, but in actual projects, more people will choose Arrange by package to view the classes under their own project package name.

2.3.3 Memory Profiler to find memory leaks

As mentioned above, the manifestation of memory leaks is that memory jitter will occur, because when a memory leak occurs, the available memory continues to decrease. When the system needs memory, GC will occur when the memory is insufficient, so memory jitter occurs.

When a memory leak occurs, Memory Profiler will show a ladder-like memory upward trend, and the memory does not decrease:

The memory leak in the above picture is quite obvious. When a memory leak occurs in actual project development, it may not be particularly obvious. It will take a long time to run to find that the memory is slowly rising. At this time, dump heap is needed to help locate.

Next, we will use the case of Handler memory leak to briefly explain how to use Memory Profiler to analyze memory leaks.

public classHandlerLeakActivityextendsAppCompatActivity{private static finalStringTAG = HandlerLeakActivity.class.getSimpleName();
​privateHandler handler = newHandler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 0) {Log.i(TAG, "handler receive msg");}}};
​@Overrideprotected void onCreate(@NullableBundle savedInstanceState) {super.onCreate(savedInstanceState);handler.sendEmptyMessageDelayed(0, 10 * 1000);}
}
复制代码

The above code is very simple. After starting the app, every time you enter HandlerLeakActivity, use Handler to delay sending messages for 10 seconds, exit the interface within 10 seconds, and repeat the operation.

1. Repeat the operation that may cause memory leaks multiple times, and the Memory Profiler heap dumps the hprof file (it is recommended to eliminate interference by GC before operation)

2. View the heap dump file hprof in Memory Profiler:

It can be found that after manual GC, Allocations shows 5 HandlerLeakActivities, and multiple Activity instances are still displayed under the heap dump Instance View, indicating that memory leaks have occurred. The specific memory leak location can be clicked on the leaked instance class object in Instance View. View, the Reference below the Instance View will display the specific reference chain.

In the new version of Memory Profiler, the Activity/Fragment Leaks checkbox is provided. Selecting it can directly find the location of possible memory leaks:

2.4 MAT

2.4.1 Introduction to MAT

  • Powerful Java Heap analysis tool to find memory leaks and memory usage

  • Generate overall reports, analyze issues, and more

  • In-depth offline use

Official website download address: www.eclipse.org/mat/downloa... , does this address have words you are familiar with? Well, that’s right, MAT is a plug-in in Eclipse, because many people now use IDEA in the development process Or Android Studio, so if you don’t want to download Eclipse, you can download the independent version of MAT. After decompression, there is an executable file of MemoryAnalyzer.exe in it. You can use it by clicking on it.

Many times we need to use this tool in conjunction with the heap dump capability of Android Studio. However, it should be noted that the hprof file generated after AS3.0 is not a standard hprof file. You need to use the command to convert it: hprof-conv After converting the original file path file path

2.4.2 Introduction to MAT usage

①. Overview: Overview information

Top Consumers

  • Objects that occupy more memory are displayed through charts. This column is more helpful when reducing memory usage.

  • Biggest Objects: Relatively detailed information

Leak Suspects

  • Quickly view suspicious points of memory leaks

②, Histogram: Histogram

  • Class Name: specifically search for a certain class

  • Objects: How many instances of a specific Class there are

  • Shallow Heap: How much memory does a single instance occupy?

  • Retained Heap: How much memory do these objects occupy in total on this reference chain?

Group by packge: Display class objects in the form of package names

List objects

  • with outgoing references: Which classes are referenced by itself?

  • with incoming references: which classes itself is referenced by

③、dominator_tree

  • Domination tree for each object

  • percentage: percentage of all objects

Right-click on the item and it will also have List objects. What is the difference between it and Histogram? The main differences are the following two points:

  • Histogram: class-based perspective analysis

  • dominator_tree: Example-based perspective analysis

④. OQL: Object Query Language, similar to retrieving content from a database

⑤, thread_overview: Detailed display of thread information, you can see how many threads currently exist in the memory

3. Practical memory jitter solution

3.1 Introduction to memory jitter

  • Definition: Frequent allocation and recycling of memory leads to memory instability

  • Performance: Frequent GC, jagged memory curve

  • Hazards: Causing lagging and, in severe cases, OOM.

3.2 Memory jitter causes OOM

  • Frequent creation of objects leads to insufficient memory and fragmentation (discontinuity)

  • Discontinuous memory slices cannot be allocated, resulting in OOM

3.3 Practical analysis

In this part, I will simulate a memory jitter, analyze the memory situation through Profiler, and locate the specific memory jitter code.

First, let's create a layout file activity_memory.xml, which contains a button to trigger the part of the code that simulates memory jitter:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/btn_memory"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="模拟内存抖动"/>
</LinearLayout>
复制代码

Then define a MemoryShakeActivity page, load the layout just now, and define a Handler in the page. When the button to simulate memory shaking is clicked, we regularly execute the code to simulate shaking in handleMessage. The entire code is easy to understand. kind:

/*** 说明:模拟内存抖动页面*/publicclassMemoryShakeActivityextendsAppCompatActivityimplementsView.OnClickListener {@SuppressLint("HandlerLeak")privatestaticHandler mHandler = newHandler(){@OverridepublicvoidhandleMessage(@NonNull Message msg) {super.handleMessage(msg);//模拟内存抖动的场景,每隔10毫秒执行一次,循环执行100次,每次通过new分配大内存for (int i=0;i<100;i++){String[] obj = newString[100000];}mHandler.sendEmptyMessageDelayed(0,10);}};@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);findViewById(R.id.btn_memory).setOnClickListener(this);}@OverridepublicvoidonClick(View view) {if (view.getId() == R.id.btn_memory){mHandler.sendEmptyMessage(0);}}@OverrideprotectedvoidonDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);}
}
复制代码

Then I started running, and I took two pictures for everyone to see. The first picture is before the code to simulate jitter is executed, and the second picture is after execution:

From the above two pictures, you can clearly see that the first memory is relatively stable, and the second memory picture has a jagged appearance, and suddenly there are frequent GCs. Do you see many small trash cans below? At this time, you can make a preliminary judgment that it should be The phenomenon of memory jitter appears, because it is more in line with its characteristics. Then drag a certain distance on the panel and it will show us the memory allocation during this period:

First double-click Allocations, and then arrange this column in order from largest to smallest, and then you will find that there are so many String arrays, and it also takes up the highest memory size (I have marked the points worthy of attention with rectangles) , we should lock this target at this time. Why are there so many arrays of String type? There is probably a problem here. Then troubleshoot what is causing this problem. It is very simple to click on the String[] line and click on any line in the Instance View panel on the right. The corresponding stack information will appear in the Allocation Call Stack panel below. The specific information is also listed above. For which line of which class, right-click jupm to source to jump to the specified source code location. In this way, we can find the location where memory jitter occurs, and then we can analyze the code and make corresponding modifications.

Process summary:

  1. Preliminary troubleshooting using Memory Profiler;

  1. Use Memory Profiler or CPU Profiler to troubleshoot code

Tips for solving memory jitter: find loops or frequent calls

4. Actual memory leak resolution

4.1 Introduction to memory leaks

Definition: There are no longer useful objects in memory

Performance: Memory jitters, available memory gradually decreases

Hazards: Insufficient memory, frequent GC, OOM

4.2 Practical analysis

Here we still use code to truly simulate a memory leak scenario. For general APP programs, the biggest problem is often Bitmap, because it consumes more memory, and it will be more obvious to use it to simulate. Well, first let’s look at the layout file activity_memoryleak.xml, which contains an ImageView control:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_memoryleak"android:layout_width="50dp"android:layout_height="50dp" />
</LinearLayout>
复制代码

Then we define a Callback interface that simulates processing certain services, and a Manager class that uniformly manages these callback interfaces:

//模拟回调处理某些业务场景publicinterfaceCallBack {voiddpOperate();
}//统一管理CallbackpublicclassCallBackManager {publicstatic ArrayList<CallBack> sCallBacks = new ArrayList<>();publicstaticvoidaddCallBack(CallBack callBack) {sCallBacks.add(callBack);}publicstaticvoidremoveCallBack(CallBack callBack) {sCallBacks.remove(callBack);}
}
复制代码

Then set Bitmap on our simulated memory leak page and set up callback listening:

/*** 说明:模拟内存泄露页面*/
public classMemoryLeakActivityextendsAppCompatActivityimplementsCallBack{@Overrideprotected void onCreate(@NullableBundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memoryleak);ImageView imageView = findViewById(R.id.iv_memoryleak);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.big_bg);imageView.setImageBitmap(bitmap);CallBackManager.addCallBack(this);}@Overridepublic void dpOperate() {}
}
复制代码

OK, our code has been written. Now let’s actually run it, and then open and close this page several times in succession to see if this code will cause a memory leak?

This is a memory picture I captured using Profiler. You can see that the entire memory has a small jitter in some places after I repeatedly switched pages. However, the overall memory rises in a ladder-like manner, and the available memory gradually decreases. It can basically be concluded that there is a memory leak in this interface. Although the Profiler tool can initially help us determine that a memory leak has occurred, it cannot determine where the memory leak occurred. This means that we still don’t know where to modify the code, so we need to use the powerful Java Heap tool at this time. , please invite MAT to appear.

First, you need to click the Dump Java Heap button in the Profiler, use the heap dump function to convert it into a file, and then click the Save button to save the file to a local directory. For example, I save it as the memoryleak.hprof file in the H drive, and then use hprof The -conv command converts it to a standard hprof file. The converted name here is: memoryleak_transed.hprof, as shown below:

Then open the MAT tool and import the converted file just generated:

Click on the Histogram to view all surviving objects in the memory, and then we can enter content in the Class Name to search for the object we want to find:

Then you can see the specific information of the object, as well as the number and memory size occupied. I found that there are actually 6 MemoryLeakActivity objects in the memory:

Then right-click List objects---->with incoming references to find all strong references leading to it:

Then right-click Path To GC Roots----->with all references, count all references and calculate the path between this object and GCRoot:

Looking at the results, we finally arrived at sCallBacks, and there is a small circle in the lower left corner of it. This is the location we are really looking for, which means that MemoryLeakActivity is referenced by the sCallBacks object of the CallBackManager class:

Based on the results found above, go to the code to find sCallBacks of CallBackManager to see what exactly is happening here?

publicstaticArrayList<CallBack> sCallBacks = new ArrayList<>();
复制代码

MemoryLeakActivity is referenced by the static variable sCallBacks. Since the life cycle of the variable modified by the static keyword is as long as the entire life cycle of the App, when the MemoryLeakActivity page is closed, we should release the reference relationship of the variable. , otherwise the above memory leak problem will occur. So solving this problem is very simple. Add the following lines of code:

@OverrideprotectedvoidonDestroy() {super.onDestroy();CallBackManager.removeCallBack(this);
}
复制代码

Process summary:

  1. Use Memory Profiler for initial observation (available memory gradually decreases);

  1. Combined with code confirmation through Memory Analyzer

5. Online memory monitoring solution

The biggest online memory problem is memory leaks. Memory jitter and memory overflow are generally related to the inability to release memory caused by memory leaks. If memory leaks can be solved, online memory problems will be reduced a lot. Online memory monitoring is actually quite difficult because we cannot use these offline tools to intuitively discover and analyze problems.

5.1 Conventional solution

①. Set scene online dump

For example, if your App has occupied a higher percentage of the maximum available memory of a single App, such as 80%, pass: Debug.dumpHprofData(); this line of code can convert the current memory information into a local file.

The whole process is as follows: More than 80% of the memory -> Memory Dump -> Return the file (note that the file may be large, keep it in wifi state and return it) -> MAT manual analysis

Summarize:

  • The Dump file is too large and is directly related to the number of objects and can be cropped.

  • High upload failure rate and difficult analysis

②. Use LeakCanary online

  • LeakCanary brings it online

  • Preset leak suspicion points

  • Found leaked postback

Summarize:

  • Not suitable for all situations. Doubts must be preset, which limits comprehensiveness.

  • Analysis is time-consuming and prone to OOM (in practice, it is found that the LeakCanary analysis process is slow, and it is very likely that OOM will occur during the analysis process)

5.2 LeakCanary customization

  • Preset suspicion points——"Automatically find suspicion points (suspect whoever has a large memory footprint, the probability of problems with large memory objects is greater)

  • Slow analysis of leaked links (analyze each object of the preset object) -> Analyze objects with large Retain Size (reduce its analysis workload and increase analysis speed)

  • Analyze OOM (map all files generated by the memory stack into memory, which takes up more memory) -> Object cropping, not all loaded into memory

5.3 Complete solution for online monitoring

  • Standby memory, key module memory, OOM rate

  • Overall and key module GC times and GC time

  • Enhanced LeakCanary automated memory leak analysis

6. Memory optimization skills

General direction of optimization:

  • memory leak

  • memory thrashing

  • Bitmap

Optimization details:

  • LargeHeap attribute (although it is a bit rogue, you should still apply to the system)

  • onTrimMemory, onLowMemory (low memory callbacks given by the system can handle some logic according to different callback levels)

  • Use optimized collections: SparseArray

  • Use SharedPreference with caution (load into memory once)

  • Use external libraries with caution (try to choose external libraries that have been verified on a large scale)

  • The business architecture is reasonably designed (the data loaded is what you can use, no memory is wasted loading useless data)

 

Guess you like

Origin blog.csdn.net/qq_25462179/article/details/132737973