preface

Recently, I have been dealing with some problems related to memory optimization. This article will make some brief records about memory, why optimize memory, and the practice of optimizing memory. Memory optimization is a long-term task, and there is a long way to go.

The resources

Android memory optimization

WeMobileDev Android memory optimization miscellaneous

Wechat Android terminal memory optimization practices

Android Performance Optimization

Meituan Androdi Memory leak automated link analysis component

directory


1. Why do we optimize memory

Memory RAM is equivalent to THE memory of PC, as the memory medium for storing temporary data during the operation of mobile APP. Back in 2008, phones had about 140MB of ram, whereas today’s phones, like the Huawei Mate 20 Pro, have 8GB of RAM. So is there no need to do memory optimization? With all this memory, we should be good. First of all, from the LPDDR RAM (low power double data rate memory) used in the phone, the performance of LPDDR4 is twice as high as LPDDR3, LPDDR4X is lower than LPDDR4 working voltage, can you guarantee that the domestic manufacturers are using good LPDDR on the 8GB memory phone? There is also a memory is so large, your APP for memory optimization, reduce the runtime memory, performance must be better, can prevent some OOM, memory is too large and may be LMK mechanism to kill, but also trigger GC, summary of the benefits of memory optimization is as follows:

  • Avoid triggering frequent GC, which in turn will avoid stuttering, which in turn will increase the user experience to the point of uninstalling the APP, which in turn will create good word of mouth

  • Prevent OOM from happening on the app

  • Avoid the probability that excessive memory is killed by the LMK mechanism


2. Basic knowledge before memory optimization

Life cycle of Java objects


Created Allocates storage space for an object and constructs an object

InUse objects are held by strong references

Invisible is not held by a strong reference, the object is still alive. (This object may be held by some static variable or JNI loaded under the virtual machine, which can cause a memory leak.)

Unreachable GC Root Unreachable. This reference is no longer held by any strong reference

Collected phase

Finalized Waiting for recycling

Deallocated recycling allocation


2. Java Memory model


There are eight operations like save and load: Lock, unlock, read, load, use, assign, store, and write

In simple terms, to transfer a variable from main memory to working memory, it must be read and load sequentially, and to write a variable from working memory to main memory, it must be store and write sequentially

3. JVM memory partition


  • The method area is used to store information about classes, constants, and static variables loaded by the VM. The string constant pool is located in the method area

  • The heap is used to store object instances, and those created by new are stored in the heap

  • The virtual machine stack is used to implement method calls. Each method call corresponds to a stack frame in the stack. The stack frame contains the local variable table, operand stack, method interface, and other method related information

  • Local method stack The local method stack is similar to the virtual machine stack except that it holds the calls to the local method. In the HotSpot virtual machine implementation, the virtual machine and the local method stack are combined.

  • Program counter The function of a program counter is equivalent to that of a PC register, except for the address of the current bytecode instruction if a Java method is being executed. If a local method is executed, the value is Undefined. It can ensure that when a thread resumes execution after an interruption, it will execute according to the instructions at the time of interruption.

  • The method area and heap are thread shared memory, while the virtual machine, local method stack and program counter are independent memory, and each thread only saves its own data

  • Registers are used to store instructions, data, and addresses, internal components of a CPU. Registers have very high read/write speed. The CPU is composed of an arithmetic unit, a controller and a register. The devices are connected by an internal bus

  • Direct memory is not part of the data area when the virtual machine is running, nor is it part of the memory area defined in the VIRTUAL machine specification, but it is frequently used. It can also cause an OutOfMemoryError

This was the case before JDK 1.8, but has since been changed to the following partition, which explains that before JDK 1.8 the method area was only a logical partition and did not physically exist independently of the heap, but in the permanent generation. So the method area can also be recycled. After Java 1.8, Hot Spot removed the permanent generation and used local memory to store the class metadata information, called the meta-space


4. Class loading

4.1. Timing of class loading

  • From load to unload, life cycle -> load, verify, prepare, parse, initialize, use, unload, seven phases

  • Validation, preparation, and parsing are collectively called joins

  • Using -> passively to refer to a static field of a parent class through a subclass does not cause the subclass to initialize

  • The passive use of -> referencing a class through an array definition does not trigger its initialization

  • Passive -> Constants are stored in the calling class’s constant pool during compilation, and subsequent references to the constant are converted to the class’s own constant pool

  • Interfaces also have an initialization process, just as classes do. The compiler still generates the “< Clint >()” class constructor for the interface to initialize the member variables defined in the interface. When an interface is initialized, it is not required that all the parent interfaces be initialized. It is initialized only when the parent interface is used.

4.2 Class loading process

  • The Class Loading process

    • Gets the binary stream that defines a class through its fully qualified name

    • Converts the static storage structure represented by this byte stream into the runtime data structure of the method area

    • A java.lang.Class object that represents the Class is generated in memory and is used as an entry point for various data about the Class in the method area

  • Validation (the loading phase is crossed with the join phase)

    • Ensure that the byte stream of the Class file contains information symbols required by the current VM and does not compromise vm security

    • File format validation (byte stream compliance with Class file format specification)

      • Whether to start with the magic number 0xCAFEBABE

      • Whether the primary and secondary versions are within the processing range of the current VM

      • Check the tag for unsupported constants in the constant pool

      • Whether any of the various index values that point to constants point to nonexistent constants or constants of unsigned type

      • CONSTANT_UTF8_info specifies whether the constant contains unsigned UTF8 encoded data

      • Each section of the Class file and whether the file itself has been deleted or has additional information

    • Metadata validation (verification of bytecode description information to ensure that there is no metadata that does not conform to the Java language specification)

      • Does this class have a parent class

      • Does the parent class of this class inherit from a class that is disallowed to inherit

      • If the class is not abstract, does it implement all the methods required in its parent class or interface

      • Whether the fields and methods in the class conflict with the parent class

    • Bytecode validation (the main purpose is to determine that the program semantics are legal and logical through data flow and control flow analysis. This stage is mainly for the method body verification analysis)

      • Ensure that the operand stack data types and instruction code sequences work together at any given time

      • Ensure that jump instructions do not jump to bytecode instructions outside of the method body

      • Ensure that the cast in the method body is valid

      • Check the status of the “Code” attribute attribute in the “StackMapTable” attribute. If the type check fails after JDK 1.7, you cannot fall back to type inference.

    • Symbolic reference validation

      • Whether a fully qualified name described by a string in a symbolic reference can find the corresponding class

      • Whether there are field descriptors in the specified class that match the method and the method and field described by the simple name

      • Whether the access type of a class, field, or method in a symbolic reference is accessible by the current class

  • Prepare (officially allocate memory for class variables (static modifier) and set the initial value of class variables (int I -> 0). All memory used by these variables will be allocated in the method area.)

  • Resolution (the process by which a virtual machine replaces a symbolic reference in a constant pool with a direct reference)

  • Initialize the

    • The “<clinit>()” method is generated by the compiler automatically collecting assignment actions for all class variables in the class and merging statements in static statement blocks.

    • The initialization phase is the execution of the class constructor “< Clint >()” method.

5. Android memory allocation

In Android, the heap is essentially a block of anonymous shared memory, managed by the underlying C library and still allocated and freed using libc functions malloc and free. Most static data is mapped to a shared process. Common static data include Dalvik Code, app Resources, SO files, and so on. And in most cases, Android implements the mechanism by which dynamic RAM areas can be shared between different processes by displaying shared memory areas such as Ashmem or Gralloc.

6, THE JVM collection algorithm and collection mechanism

For details, see Understanding Java Virtual Machine Version 2. The following is a brief overview of the reclamation mechanism and the reclamation algorithm

  • Memory allocation consists of three regions: young generation Eden Survivor1 Survivor2 (1/8) Old age, permanent generation. It is first allocated in Eden, and then recycled or entered into the old age or eternal generation according to the age calculation.

  • Before garbage collection: First determine whether the object is alive

    • Reference counting, where it is referenced, it increases by 1, when it is invalidated, it decreases by 1, =0 means it can’t be referenced again, if two objects refer to each other, it can’t be reclaimed

    • Reachability analysis, by determining that an object is unreachable when there is no reference chain connected to GC ROOT

  • After the object is unreachable, the system filters the object to check whether it needs to be executed

  • Finally, it is collected by garbage collection algorithm

    • The mark-sweep algorithm has two stages, but the mark-sweep is insufficient: it is inefficient, and it generates a large number of discontinuous memory fragments

    • The replication algorithm divides the available memory into two equal pieces according to the capacity, and only uses one piece at a time. If not enough, it shrinks the memory by half. When there is no large amount of memory reclamation, it is too wasteful. (For the new generation, there is not so much memory to reclaim)

    • The mark-collation algorithm clears the tags and moves the surviving objects to one end. (Applicable to old age)

ps : There are actually about six recycling algorithms, so if you look at them, They are Reference Counting, mark-sweep, Copying, mark-compact and Incremental collection (Collecting), Generational Collecting

Android Low Memory Killer (LMK) mechanism

First, understand the system’s process classification, sorted by priority

  • Foreground process

  • Visible process

  • Service process

  • Background processes

  • Empty process (an empty process consumes 10 MB of memory, so it is necessary to start a process to handle some business.)

The LMK mechanism is that the lower the priority, the easier to kill,

8. Four references

  • Strong references, long life cycle, never recycled

  • Soft reference, short life cycle, memory is insufficient, will reclaim

  • A weak reference has a shorter life cycle than a soft reference. Once a weak reference object is found, it will be reclaimed regardless of whether the current memory space is sufficient.

  • Virtual references, the shortest lifetime, if an object only holds virtual references, then it is equivalent to no reference, at any time can be collected by the garbage collector


Three, memory three problems

1. Memory jitter

Possible cause: Frequent GC, memory reallocation, and memory reclamation result in unstable memory, showing a sawtooth shape on the memory. We can’t optimize the collection strategy (except for the gods), we can only start by reducing GC.

2. Memory leak

The memory occupied by the object was not reclaimed after it became useless. Why is that? Because THE GC Roots hold the reference, or the object is reachable to the GC Roots. This results in reduced available memory, frequent GC, and memory jitter.

Common memory leak scenarios

  • The life cycle of a singleton is equal to the life cycle of the entire application. If you hold an Activity, it will cause the Activity to be unrecyclable

  • The most common example of anonymous inner classes is when the Handler holds a reference to an Activity, causing a memory leak

  • Resources such as File and Bitmap are closed or recycle. EventBus is not unregistered after being registered

  • Static variables of a class hold big data objects or hold non-static inner class instances

  • When the ListView slides fast, it does not use the cache View, resulting in the frequent creation of a large number of objects, this is generally not concerned about, this year, all need to use ViewHolder, as for the RecyclerView, You can free the image reference operation in the onViewRecycled method of RecyclerViewAdapter.

  • As long as the WebView is used once in the application, the memory will not be freed. Generally, you can start a separate process for the WebView, use AIDL to communicate with the main process, and when destroying, just shut down the process.

Common optimization schemes

  • SparseArray and SparseInt replace HashMap

  • Avoid automatic boxing and unboxing of basic data types, such as int with 4 bytes and Integer objects with 16 bytes

  • Use IntDef and StringDef instead of enumeration whenever possible

  • Use the LruCache method to cache large resources, such as preloading

  • Bitmap optimization, do not need good quality, generally choose ARGG_565 is enough, as mentioned below. InSampleSize, inScaled, inBitmap, and so on can be fully utilized. Note that before Android 4.4, you can only reuse the same size Bitmap memory area. After Android 4.4, you can reuse any Bitmap memory area. As long as the memory is larger than the Bitmap that will be unaccompanied by memory

  • When memory is too low, use the onTrimMemory/onLowMemory method to free the image cache, static cache, etc

  • Use string. valueOf instead of “”+int as much as possible. String concatenation uses StringBuffer and StringBuilder

  • Note whether the anonymous inner Runnable class holds a reference to an external class or Activity. It is necessary to release the Activity reference and prohibit using anywhere new Runnable

3. Memory overflow

OOM, the program failed to allocate enough memory when applying for memory, causing the program to crash. A memory leak, which reduces the available memory, can also lead to a memory overflow in certain appropriate scenarios. In fact, a lot of OOM are generated by improper image processing, in reality, the general use of Glide image loading framework, basically to the third party library processing.

Use the following command to check the memory size allocated by the mobile phone system to the APP

adb shell getprop | grep dalvik.vm.heapsizeCopy the code

Iv. Memory optimization strategy

Memory optimization, the focus is on image related. In view of so replait/cannot replait and so on optimization, readers are interested in can review, general access third-party framework, basic OOM and Native memory leaks can be captured, the key how to do prevention, through the online memory trend to determine whether the need for a large probability to OOM.

Changes in Android Bitmap memory allocation

  • Before Android 3.0, Bitmap objects are put in heap, pixel data is put in Native species. If we do not call Recycle manually, Bitmap Native memory reclamation completely depends on Finalize callback, and this time is not controllable

  • From Android 3.0 to Android 7.0, Bitmap objects and pixel data are placed in the Java heap, so that the Bitmap memory is reclaimed along with the object even without calling recycle. But Bitmap is a big memory consumer, put in the Java heap, the maximum App memory will be used up immediately, and put in the heap, not well optimized, there will be frequent GC

  • In Android 8.0, pixel data is put into Native, which realizes fast release and recycling together with objects. In addition, Hardware Bitmap is added to reduce the image memory and improve the rendering efficiency

If you’re interested, check out the Fresco photo gallery process for putting your images on Native

NativeBitmap = nativeCreateBitmap(dstWidth, dstHeight, nativeConfig, 22); // Native Bitmap = nativeBitmap (dstWidth, dstHeight, nativeConfig, 22); / / step 2: apply for a normal Java Bitmap Bitmap srcBitmap = BitmapFactory. DecodeResource (res, id); // Step 3: Use Java Bitmap to draw content into Native Bitmap mnativecanvas.setBitMap (nativeBitmap); mNativeCanvas.drawBitmap(srcBitmap, mSrcRect, mDstRect, mPaint); // Step 4: Release the Java Bitmap memory srcbitmap.recycle (); SrcBitmap = null;Copy the code

Next, document some practical optimization strategies

1. Unified photo library

Fold up image calls, reduce unnecessary image library, and optimize package volume. Glide is generally sufficient for image related operations.

Glide loading image principle flowchart is as follows



2. Unified management

Including all and image related interface operations, image caching strategy and so on, can be unified together.

Set the configuration of an image, for example, a less demanding image, using bitmap.config.argb_4444, and use the following formula to see how much memory can be saved

Width * (desityDip/drawable of the screen) * Height * (desityDip/drawable of the screen) * 4 (bitmap.config.argb_8888) Each pixel is 4 bytes.) Here's an example: If the drawable- HDPI density is 240, the memory size of a 600 * 600 image is 600 * (320/240) * 600 * (320/240) = 2560000 bytes = 2.56mCopy the code

3. Avoid unnecessary processes

As mentioned in the previous page, an empty process takes up 10MB of memory. If you do memory optimization, for some operations, you don’t need to start the process, you need to use the process, and introduce some process communication pits, then I don’t have to say anything. If we can improve the speed, improve the user experience, make it easier to manage the APP, make it easier to optimize the APP, then it’s worth exploring.

4. Picture detection

When optimizing the package size, we called the byte MCImage, which can also be used to detect large images, but this is based on offline.

A duplicate image is one in which the Bitmap pixel data is identical, but multiple different objects exist. Typically used with the Hprof analysis tool, the image and reference stack output of the duplicated Bitmap is automatically output. HAHA library is used to detect duplicate images. Due to my limited ability, I have not done this step in practice. There is a training project in geek time, but for small companies, it is not necessary to access the project, and there are some compatibility issues to consider.

5, the establishment of thread application memory monitoring system

This can be done, and personally feel very practical, the following simple description write how to build

  • According to user sampling, PSS (T via debug.Memoryinfo), Java heap, picture total memory, and upload to the server at regular intervals. The server also needs to set a switch, because collecting GC times will affect the performance of the phone, it is recommended to sample the test in grayscale.

  • The server calculates indicators as follows

    Memory anomaly rate

    Memory UV anomaly rate = PSS over 400 MB of UV/acquisition UVCopy the code

    Peak rate

    Memory UI hit rate = JAVA heap usage exceeds 85% of maximum heap limit/collection UV e.g. Long javaMax = Runtime.maxMemory (); long javaTotal = runtime.totalMemory(); long javaUsed = javaTotal - runtime.freeMemory(); // Java memory usage exceeds 85% of the maximum limitfloat proportion = (float) javaUsed / javaMax;Copy the code
  • By graphing the data on the front end, for example, through a memory change curve, we can clearly monitor whether new memory is associated with problems

Interested students can check out meituan Androdi memory leak automated link analysis component

6, memory pocket strategy

The main purpose is to kill the process and restart it in the appropriate scenario before it is close to triggering a system exception, without the user being aware of it, so that the application memory usage returns to normal.

This strategy can be quietly used between 2:00 a.m. and 5:00 a.m., or when the main screen is out of the background for more than 30 minutes, or when the Java Heap size exceeds 85% of the maximum available for the current process. As for the bottom line strategy, try using the JD.com APP to sift through items and look at them for an hour, and you’ll find something amazing.


Five, memory optimization commonly used tools

This section only describes the tools used in the analysis, such as top, ADB Dumpsys meminfo to view memory information

Adb shell am start -w [packageName]/[packagena.mainActivity] adb shell, Dumpsys meminfo [PKG] Include the number of current activities

1, the Memory Profiler


The following is a brief introduction to the meaning of each field in the float frame.

  • Object memory allocated by Java from Java or Kotlin code

  • Native Object memory allocated from C++ code

  • The memory used by the Graphics buffer queue to display pixels (including GL surfaces, GL textures) to the screen

  • Stack The amount of memory used by the native and Java stacks in an application, which is usually related to how many threads the application is running

  • Code Is the memory used to process Code and resources such as dex bytecode, optimized or compiled DEX codes, so libraries, and fonts

  • Other The system is not sure how to allocate memory

  • Allocated Java/Kotlin objects Allocated by the application. This number is not included in C or C++ Allocated objects

  • Total Total memory size used

Click on the class, and the class list appears with the following field meanings


  • Shallow Size Specifies the Size of memory occupied by the object itself, excluding the objects it references

  • Retained Size Retained Size of a Retained object + Retained Size of a Retained object that can be referenced directly or indirectly by the object. This is the total Retained Size of the Retained object that was collected by GC

  • Mobile phones after Native Size 8.0 will display, mainly reflecting the pixel memory used by Bitmap (after 8.0, moved to Native).

  • Allocated heap The Allocated heap, that is, the memory size occupied by the APP

One of the worst things about using profilers is that sometimes it can cause APP cards or unresponsiveness. Don’t think it’s your own code.

When the App is in interface A, then open page B, then go back to page A, and perform GC several times. If the Java indicator memory is not restored, then it is highly likely that optimization is needed, and then analyze with the tools of the following two boxes

2, LeakCanary

Principle analysis of Android memory leak detection tool LeakCanary

Generally used for offline integration, to see whether there is a memory leak problem, in the analysis of the page card immediately, should be integrated LeakCanary removed, to avoid error

3, MAT

A powerful Java Heap analysis (download link) tool that generates overall reports, analyzes memory problems, etc., is commonly used by small and medium-sized companies, while large companies may use JHT.

Generally, it is also used offline in depth. As for online monitoring, it is currently realized through the fifth point of the above optimization strategy. After analyzing the problem, these tools are used offline for analysis.

You can use Android Studio Profiler to take a Java Heap file and convert it to a Hprof file as follows

Hprof -conv before.hprof after. Hprof //hprof is in the platform-tools directory of android Studio SDKCopy the code

The transformed standard HProf is then turned on via MAT


Click on the red box below, such as Histogram, to search for activities, see the call chain, see the root cause of memory leaks, and support leak alerts

Here are two simple introduction of MAT use introduction of MAT use introduction of Android memory optimization


6. Bit operation


public static int oddApperance1(int[] A, int n) {
	int e = 0;
	for (int i = 0; i < n; i++) {
		e = e ^ A[i];
	}
	return e;
}
Copy the code


Note 7