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:

  1. Class- An object loaded by the system ClassLoader
  2. Thread- A living Thread
  3. Stack Local- The Local variable or parameter of the Java method
  4. JNI Local – The Local variable or parameter of the JNI method
  5. JNI Global – Global JNI reference
  6. 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

  1. 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.
  2. LeakCanary Analyzes the leak link slowly. Therefore, it is modified to analyze only objects with a large Retain size.
  3. 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

  1. General monitoring indicators: Standby memory, memory occupied by key modules, and OOM rate
  2. Monitor the NUMBER and time of GC in APP life cycle and key module interface life cycle
  3. 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