This article is adapted from an online course
The background,
In terms of memory management, the JVM has a garbage collection mechanism that automatically allocates and frees memory at the virtual machine level, so there is no need to allocate and free a chunk of memory in code like with C/C++. Android’s memory management is similar to that of the JVM, where objects are allocated memory using the new keyword, and the release of memory is reclaimed by GC. And Android has a Generational Heap Memory model for Memory management, where when Memory reaches a certain threshold, the system automatically frees the Memory that can be freed according to different rules. Even with the memory management mechanism, improper use of memory can cause a series of performance problems, such as memory leaks, memory jitter, allocating a large number of memory objects in a short period of time, and so on.
Second, optimization tools
1, the Memory Profiler
Memory Profiler is a Memory detection tool that provides real-time graphics to display Memory information. It can detect Memory leaks, jitter, heap dumps, force GC, and track Memory allocations.
Android Studio opens the Profiler tool
Observe the Memory curve. If the curve is relatively flat, it indicates that the Memory is properly allocated. If there is a large fluctuation, a Memory leak may occur.
GC: GC can be triggered manually
Dump: dumps the current Java Heap information
Record: Records memory information within a period of time
After clicking the Dump
You can view the current memory allocation object
Allocations: Number of objects were allocated
Native Size: indicates the Size of the Native memory
Shallow Size: The Size of the memory occupied by the object itself, excluding the object referenced by it
Retained Size: Retained Size of the object = Shallow Size of the object itself + Shallow Size of the object that can be accessed directly or indirectly. In other words, Retained Size is the sum of the memory that can be reclaimed after the Retained object is Gc
Click Bitmap Preview to Preview the image, which is helpful for viewing the memory usage of the image
Click on the Record after
You can record the memory allocation in a period of time, view the size of each object allocation and call stack, object generation location
2, Memory Analyzer (MAT)
Java Heap analysis, a more powerful tool than Memory Profiler, can accurately look for Memory leaks and usage, as well as generate overall reports for problem analysis and more.
MAT is generally used to analyze problems offline with Memory Profiler. Memory Profiler can intuitively detect Memory jitter, and then generate HDProf files for in-depth analysis and locating Memory leaks through MAT.
The specific use will be explained with examples below
3, LeakCannary
Leak Cannary is an offline monitoring tool that can automatically detect memory leaks. The specific principle can be learned by yourself.
Github link: github.com/square/leak…
Three, memory management
3.1 Memory Area
Java memory is divided into method area, heap, program counter, local method stack, virtual machine stack five areas.
Thread dimension is divided into thread shared area and thread isolated area, method area and heap are thread shared, program counter, local method stack, virtual machine stack are thread isolated, as shown in the figure below
Methods area
- Thread shared areas for storing class information, static variables, constants, and code data compiled by the just-in-time compiler
- OOM occurs when memory allocation requirements cannot be met
The heap
- The thread shared area, which is the largest chunk of memory managed by the JAVA VIRTUAL machine, is created when the virtual machine is started
- Where object instances are held, almost all of which are allocated on the heap, the main area managed by GC
The virtual machine stack
- Thread private area, where each Java method execution creates a stack frame to store information about local variables, operand stacks, dynamic links, method exits, and so on. Method from the beginning of execution to the end of the process is the stack frame in the virtual machine stack process
- The local variable table stores basic data types, object references, and returnAddress types known at compile time. The required memory space is allocated at compile time, and the space to locally change tables in a frame when entering a method is fully determined and does not need to be changed at run time
- If the stack depth requested by the thread is greater than the maximum depth allowed by the VM, a SatckOverFlowError is raised
- An OutOfMemoryError is thrown if sufficient memory cannot be allocated during vm dynamic expansion
Local method stack
- It provides services for Native methods in virtual machines. There are no mandatory provisions on the language, data structure and usage mode used in the local method stack. Virtual machines can implement it by themselves
- The size of the memory area is not fixed and can be dynamically expanded as needed
Program counter
- A small memory space, thread private, that stores a bytecode line number indicator for execution by the current thread
- The bytecode interpreter changes the value of this counter to select the next bytecode instruction to execute: branch, loop, jump, and so on
- Each thread has a separate program counter
- The only area in the Java virtual machine that is not OOM
3.2 Object survival judgment
Reference counting method
- Add a reference counter to an object. Each time a reference is made, the counter is incremented by 1 and the counter is decayed by 1. The object is unavailable when the reference counter is 0
- This method is simple and efficient, but cannot solve the cross-reference problem. Mainstream VMS generally do not use this method to determine whether objects are alive
Accessibility analysis
- Starting from some objects called “GC Roots”, the search path is called the reference chain. When an object has no reference chain to GC Roots, the object is unusable and can be recycled
- Objects that can be called GC Roots: objects referenced in the virtual machine stack, objects referenced in class static attributes in the method area, objects referenced in constants in the method area, and objects referenced in the local method stack
GC Root has the following types:
- Class- An object loaded by the system ClassLoader
- Thread- A living Thread
- Stack Local- The Local variable or parameter of the Java method
- JNI Local – The Local variable or parameter of the JNI method
- JNI Global – Global JNI reference
- Monitor Used – Monitor objects Used for synchronization
3.3 Garbage collection algorithm
Mark clearing algorithm
The mark clearing algorithm has two stages: first, mark the objects that need to be recycled, and uniformly recycle all marked objects after the completion of the mark.
Disadvantages:
- Efficiency problem: Both marking and cleaning processes are inefficient
- Space issues: There are many discrete memory fragments after the tag is cleared, which can lead to the problem of not finding enough contiguous space when large objects need to be allocated and having to trigger GC
Replication algorithm
Divide the available memory into two pieces of the same size according to the space, use one piece at a time, and then copy the surviving objects to another piece of memory when the memory is used up, and then clear the whole object in this piece of memory area. Each time the entire half of the memory reclamation, will not cause fragmentation problems, simple and efficient implementation.
Disadvantages:
- Need to reduce the memory to half of the original, space is too expensive
Tag sorting algorithm
The marking process is the same as the mark cleaning algorithm, but the cleaning process does not clean the recyclable objects directly, but moves all the living objects like one end, and then concentrates the cleaning into memory beyond the end boundary.
Generational collection algorithm
Modern VIRTUAL machine garbage collection algorithms use generational collection algorithms to collect garbage. Memory is divided into the new generation and the old generation according to the different life cycles of objects, and the most appropriate algorithm is adopted according to the characteristics of each generation.
- There are few living objects in the new generation, and a large number of objects die in each garbage collection. Generally, the replication algorithm is adopted, and garbage collection can be realized only at the cost of copying a small number of living objects.
- There are many living objects in the old age and there is no extra space for allocation guarantee, so we must use the mark clearing algorithm and the mark sorting algorithm to recycle.
4. Memory jitter
Frequent memory allocation and reclamation result in memory instability
- Frequent GC and jagged memory curves can lead to stalling
- Frequent object creation leads to insufficient memory and fragmentation
- Discontinuous memory fragments cannot be released, causing OOM
4.1 Simulating memory jitter
Execute this code
private static Handler mShakeHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); // Create objects frequently to simulate memory jitterfor(int index = 0; index <= 100; index ++) { String strArray[] = new String[100000]; } mShakeHandler. SendEmptyMessageDelayed (0, 30); }};Copy the code
4.2 Analysis and Locating
Use the Memory Profiler tool to view Memory information
Found that the memory curve from the original smooth curve into a sawtooth
Click record to record memory information and find the location of memory jitter. It is found that the String object ShallowSize is very abnormal and can be directly located to the code location by Jump to Source
5. Memory leaks
Definition: objects in memory that are no longer used and cannot be reclaimed
Symptom: Memory jitter reduces available memory, resulting in frequent GC, delay, and OOM
5.1 Simulating memory leaks
Simulate the memory leak code, repeatedly enter and exit the Activity
Public class MemoryLeakActivity extends AppCompatActivity implements CallBack{@override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_memoryleak); ImageView imageView = findViewById(R.id.iv_memoryleak); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash); imageView.setImageBitmap(bitmap); / / add static class reference CallBackManager. AddCallBack (this); } @Override protected voidonDestroy() {
super.onDestroy();
// CallBackManager.removeCallBack(this);
}
@Override
public void dpOperate() {
// do sth
}
Copy the code
5.2 Analyzing and Locating
Using the Memory Profiler tool to look at the Memory curve, we found that the Memory is increasing
If you want to analyze and locate specific locations of memory leaks, use MAT
Start by generating the hprof file
Click dump to convert the current memory information into hprof file. The generated file needs to be converted into a file that MAT can read
Run the conversion command (Android/ SDK /platorm-tools).
Hprof-conv Just generated the hprof file memory-mat.hprofCopy the code
Use MAT to open the hprof file you just converted
Click on Historygram to search for MemoryLeakActivity
You can see that eight MemoryLeakActivity has not been released
View all referenced objects
Check the GC Roots reference chain
You can see that GC Roots are the CallBackManager
Fix the problem by removing the current reference when the Activity is destroyed
@Override
protected void onDestroy() {
super.onDestroy();
CallBackManager.removeCallBack(this);
}
Copy the code
6. MAT analysis tool
Overview
Current memory information
Histogram
List all instances of the object and the size of the instance, can be sorted by package
You can check the number of Activity instances under the application package name and check whether there is a memory leak. In this case, you can find that there are eight Activity instances in the memory
View the chain of references for activities that are not released
Dominator_tree
If Histogram is the dominant tree of all current instances, as distinguished from Histogram, where Histogram is the class dimension, and Dominator_tree is the instance dimension, you can view the percentage and reference chain of all instances
SQL
Query related class information using SQL statements
Thread_overview
View information about all threads
Top Consumers
It is helpful to reduce the memory stack and optimize the available memory by graphically displaying objects with high memory usage
Leak Suspects
Memory leak analysis page
Locate the memory leak location
7. ARTHook detects inappropriate images
7.1 Obtaining Memory Occupied by Bitmap
- Through the getByteCount method, but you need to get it at run time
- Width * height * Memory occupied by a pixel * Compression ratio of the resource directory where the image is located
7.2 Large view of detection
When the image load exceeds the size of the control itself, memory is wasted, so it is important to detect improper images for memory optimization.
ARTHook way to detect unreasonable images
ARTHook method is an elegant way to get inappropriate images, less intrusive, but generally used offline due to compatibility issues.
Introduce epic open source library
implementation 'me. Weishu: epic: 0.3.6'Copy the code
Implementing Hook methods
public class CheckBitmapHook extends XC_MethodHook {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
ImageView imageView = (ImageView)param.thisObject;
checkBitmap(imageView,imageView.getDrawable());
}
private static void checkBitmap(Object o,Drawable drawable) {
if(drawable instanceof BitmapDrawable && o instanceof View) {
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if(bitmap ! = null) { final View view = (View)o; int width = view.getWidth(); int height = view.getHeight();if(width > 0 && height > 0) {
if(bitmap.getWidth() > (width <<1) && bitmap.getHeight() > (height << 1)) {
warn(bitmap.getWidth(),bitmap.getHeight(),width,height,
new RuntimeException("Bitmap size is too large")); }}else {
final Throwable stacktrace = new RuntimeException();
view.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override public boolean onPreDraw() {
int w = view.getWidth();
int h = view.getHeight();
if(w > 0 && h > 0) {
if (bitmap.getWidth() >= (w << 1)
&& bitmap.getHeight() >= (h << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stacktrace);
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
return true; }}); } } } } private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) { String warnInfo = new StringBuilder("Bitmap size too large: ")
.append("\n real size: (").append(bitmapWidth).append(', ').append(bitmapHeight).append(') ')
.append("\n desired size: (").append(viewWidth).append(', ').append(viewHeight).append(') ')
.append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
.toString();
LogUtils.i(warnInfo);
Copy the code
The Hook is injected when the Application is initialized
DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
DexposedBridge.findAndHookMethod(ImageView.class,"setImageBitmap", Bitmap.class, new CheckBitmapHook()); }});Copy the code
Eight, online memory monitoring
8.1 General Scheme
Conventional plan 1
Obtain the current memory size in a specific scenario. If the current memory size exceeds 80% of the maximum memory size, Dump (debug. dumpHprofData()) the current memory, upload the hprof file at an appropriate time, and use the MAT tool to manually analyze the file.
Disadvantages:
- A large Dump file is positively correlated with the usage time and object tree
- Large files lead to high upload failure rate and difficult analysis
Conventional plan 2
Bring LeakCannary online, add a preset suspect point, and monitor memory leaks at the suspected point. Memory leaks are sent back to the server.
Disadvantages:
- With low generality, it is necessary to preset the doubt point, and the place without the preset doubt point cannot be monitored
- LeakCanary Analysis is time-consuming and memory consuming, and an OOM may occur
8.2 LeakCannary custom retrofit
- Change the need to preset the doubt point to automatically find the doubt point, automatically set the doubt point in the object class which occupies a large memory in the previous memory.
- LeakCanary Analyzes the leak link slowly. Therefore, it is modified to analyze only objects with a large Retain size.
- The analysis process is OOM because LeakCannary will load all analysis objects into the memory during analysis. We can record the number and usage of analysis objects and cut the analysis objects so that they are not all loaded into the memory.
8.3 Complete Scheme
- General monitoring indicators: Standby memory, memory occupied by key modules, and OOM rate
- Monitor the NUMBER and time of GC in APP life cycle and key module interface life cycle
- Bring the custom LeakCanary online to automate the analysis of memory leaks online
Series of notes
Deep Performance Optimization for Android –APP Startup Optimization
Deep Performance Optimization for Android –APP Memory Optimization
Android Deep Performance Optimization –APP Layout Optimization
“Deep Performance Optimization for Android –APP Caton Optimization”
Deep Performance Optimization for Android –APP Thread Optimization
“Android Deep Performance Optimization –APP Network Optimization”
“Android Deep Performance Optimization –APP Battery Optimization”
“Deep Performance Optimization for Android –APP Crash Optimization”
Continue to pay attention to add wechat official account: Yu Xiang notes