Android | Those things about OOM

Author: 345、

foreword

The Android system will have a maximum memory limit for each app. If this limit is exceeded, OOM will be thrown, which is Out Of Memory . It is essentially an exception thrown, usually after the memory exceeds the limit. The most common OOM is the OOM caused by memory leaks (a large number of objects cannot be released), or the required memory size is greater than the allocated memory size. For example, loading a very large image may cause OOM.

Common OOMs

heap overflow

Heap memory overflow is the most common OOM, usually because the heap memory is full and cannot be reclaimed by the garbage collector , resulting in OOM.

thread overflow

The maximum number of threads allowed by different mobile phones is different. On some mobile phones, this value is modified to a very low value, and it is more prone to the problem of thread overflow.

FD quantity overflow

File descriptor overflow, when a program opens or creates a new file, the system will return an index value, pointing to the record table of the file opened by the process, for example, when we open a file with an output stream file, the system will return us an FD , FD may leak.

Insufficient virtual memory

When creating a new thread, the bottom layer needs to create a JNIEnv object and allocate virtual memory. If the virtual memory is exhausted, the thread creation will fail and OOM will be thrown.

The memory difference between Jvm, Dvm, and Art

Android uses the Java language-based virtual machine Dalvik / ART, and both Dalvik and ART are based on JVM , but it should be noted that the virtual machine in Android is different from the standard JVM because they need to run on Android devices , so they have different optimizations and limitations.

In terms of recycling, Dalvik only fixes one recycling algorithm, while the ART recycling algorithm can be selected on demand during runtime, and ART has the ability to organize memory to reduce memory holes.

JVM

JVM is a fictitious computer, which is realized by simulating various computer functions on an actual computer. It has a complete (virtual) hardware architecture and a corresponding instruction system, and its instruction set is based on a stack structure. The use Java虚拟机is to support programs that are independent of system operations and can run on any system.

The memory managed by the JVM is divided into the following parts:

  • method area

    Shared by each thread lock, it is used to store class information, constants, static variables, etc. that have been loaded by the virtual machine. When the method area cannot meet the memory allocation requirements, an OutOfMemoryError exception will be thrown.

    • constant pool

      The constant pool is also a part of the method area, which is used to store various arguments and symbol references generated by the compiler. The most used one is String. When new String is called intern, it will check whether there is such a string in the constant pool. If there is one, return it, if not, create one and return it.

  • java heap

    The largest piece of memory in the virtual machine memory. All objects created through new will be allocated in the heap memory. It is the largest piece of memory in the virtual machine, and it is also the part that gc needs to reclaim. At the same time, OOM is also prone to occur here .

    From the perspective of memory recovery, since most collectors now use the generational collection method, it can also be subdivided into new generation, old generation, etc.

    According to the provisions of the Java virtual machine, the Java heap can be in a physically discontinuous space, as long as it is logically continuous. If there is no memory that can be allocated, an OutOfMemoryError exception will occur.

  • java stack

    The thread is private , used to store all the data when the java method is executed, and it is composed of a stack frame. A stack frame represents the execution of a method. The execution of each method is equivalent to a stack frame in the virtual machine from the stack to the stack. the process of. The stack frame mainly includes local variables, stack operands, dynamic links, etc.

    The Java stack is divided into operand stack, stack frame data and local variable data. The local variables allocated in the method are on the stack. At the same time, each method call will accompany the stack frame on the stack. The size of the stack is a double-edged sword. Too small may cause stack overflow, especially when there is recursion, a large number of loop operations. If it is too large, it will affect the number of stacks that can be created. If it is a multi-threaded application, it will cause memory overflow.

  • native method stack

    The effect is basically similar to the java stack, the difference is that it is used to serve the native method.

  • program counter

    It is a small space, and its function can be regarded as the line number indicator of the current thread lock execution bytecode, which is used to record the address of the bytecode instruction executed by the thread, so that the thread can be restored to the correct execution position when switching .

DVM

Originally named Dalvik, it is a virtual machine designed by Google for the Android platform. It is also a JAVA virtual machine in essence. It is the basis for running Java programs in Android . Its instructions are based on the register architecture and execute its unique file format -dex.

DVM runtime heap

The heap structure of DVM is different from that of JVM, mainly reflected in the fact that the heap is divided into Active heap and Zygote heap . Zygote is a virtual machine process and a virtual machine instance incubator. The zygote heap is the classes, resources and objects that the Zygote process preloads when it starts. In addition, the instances and arrays we create in the code are stored in in the Active heap.

The reason why the Dalvik heap is divided into two is mainly because Android creates a new zygote process through the fork method, in order to avoid data copying between the parent process and the child process as much as possible.

Dalvik's Zygote stores preloaded classes that are Android core classes and Java runtime libraries. This part is rarely modified. In most cases, the child process and the parent process share this area, so there is no need for garbage collection for this class . Active, as the heap of instance objects created in the program code, is the key area of ​​garbage collection, so the two heaps need to be separated.

DVM recycling mechanism

DVM's garbage collection strategy defaults to the mark-and-sweep algorithm (mark-and-sweep), and the basic process is as follows

  1. Marking phase: start traversing from the root object, mark all reachable objects, and mark them as non-garbage objects
  2. Clear phase: traverse the entire heap and clear all unmarked objects
  3. Compaction phase (optional): compresses all objects in the inventory together to reduce memory fragmentation

It should be noted that the DVM garbage collector is based on the mark-and-sweep algorithm. This algorithm will generate memory algorithms, which may reduce the efficiency of memory allocation. Therefore, DVM also supports generational recovery algorithms, which can better deal with memory fragmentation problems.

In generational garbage collection, memory is divided into different ages, and each age is processed using a different garbage collection algorithm. The young generation uses the mark-copy algorithm, and the old generation uses the mark-clear method, which can better balance memory allocation efficiency. and garbage collection efficiency

ART

ART is a virtual machine introduced in Android 5.0. Compared with DVM, ART uses AOT (Ahead of Time) compilation technology , which means that it converts the bytecode of the application into native machine code instead of in Interpret the bytecode one by one at runtime, this compilation technology can improve the execution efficiency of the application and reduce the startup time and memory usage of the application

The difference between JIT and AOT
  • Just In Time

    The DVM uses a JIT compiler, which translates part of the dex bytecode into machine code in real-time each time the application runs. During the execution of the program, more code is compiled and cached. Since JIT only translates part of the code, it consumes less memory and occupies less physical memory space

  • Ahead Of Time

    ART has a built-in AOT compiler. During the application installation, she compiles the dex bytecode into machine code and stores it on the device's memory. This process is designed to happen when the application is installed on the device. Since JIT compilation is no longer required, the code Execution speed is much faster

ART runtime heap

Different from DVM, ART adopts a variety of garbage collection schemes, and each scheme will run different garbage collectors. The default is to use the CMS (Concurrent Mark-Sweep) scheme, which is concurrent mark removal. This scheme mainly uses sticky-CMS and partial-CMS. According to different schemes, the space of the ART runtime heap will also be divided differently. By default, it is composed of four areas.

They are composed of Zygote, Active, Image and Large Object, among which the function of Zygote and Active is the same as that of DVM, the Image area is used to store some preloaded classes, and the Large Object is used to allocate large objects (default size 12kb), where Zygote and Image are shared between processes,

LMK memory management mechanism

LMK (Low Memory Killer) is part of the memory management mechanism of the Android system. LMK releases unnecessary processes in the system when the memory is insufficient to ensure the normal operation of the system.

The underlying principle of the LMK mechanism is to use the kernel OOM mechanism to manage memory. When the system memory is insufficient, the kernel will allocate memory to important processes according to the priority of each process, and will end unimportant processes at the same time to avoid system crashes.

The usage scenarios of the LKM mechanism include:

  • Insufficient system memory: The LMK mechanism helps the system manage memory to ensure the normal operation of the system
  • Memory leak: When there is a memory leak in the application, LMK will release the leaked memory to ensure the normal operation of the system
  • Process optimization: help the system manage processes to ensure reasonable utilization of system resources

When the system memory is tight, the LMK mechanism can release memory by ending unimportant processes to ensure the normal operation of the system.

But it can also cause app instability if used incorrectly.

Why does OOM occur?

OOM appears because the Android system has limited the heap of the virtual machine. When the requested space exceeds this limit, OOM will be thrown. The purpose of this is to allow the system to allow more processes to reside in memory at the same time , so that the program does not need to be reloaded into the memory every time when the program starts, which can give the user a faster response

Android gets the allocated memory size

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.memoryClass

Returns the approximate per-application memory class for the current device. This lets you know how much memory limit you should impose on your application to make the whole system work best. The return value is in megabytes; the baseline Android memory class is 16 (which happens to be the Java heap limit for these devices); some devices with more memory may return a number of 24 or even higher.

The mobile phone memory I use is 16 g, and the call returns 256Mb,

manager.memoryClass corresponds to dalvik.vm.heapgrowthlimit in build.prop

Apply for a larger heap memory

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.largeMemoryClass

The maximum memory limit that can be allocated needs to be enabled by setting android:largeHeap="true" in the manifest file

manager.largeMemoryClass corresponds to dalvik.vm.heapsize in build.prop

Runtime.maxMemory

Get the maximum memory limit that can be obtained by the process, which is equal to one of the above two values

/system/build.prop

This directory is a file related to Android memory configuration, which stores data such as the system memory limit. Execute the adb command to see the memory related information of Android configuration:

adb shell
cat /system/build.prop

By default, it cannot be opened, there is no permission, and root is required

After opening, find the configuration related to dalvik.vm

dalvik.vm.heapstartsize=5m	#单个应用程序分配的初始内存
dalvik.vm.heapgrowthlimit=48m	#单个应用程序最大内存限制,超过将被Kill,
dalvik.vm.heapsize=256m  #所有情况下(包括设置android:largeHeap="true"的情形)的最大堆内存值,超过直接oom。

When android:largeHeap="true" is not set, oom will be triggered as long as the requested memory exceeds the heapgrowthlimit, and when android:largeHeap="true" is set, oom will be triggered only if the memory exceeds the heapsize. The heapsize is already the maximum memory that the application can apply for (the memory requested by native is not included here).

OOM demo

Heap memory allocation failed

Heap memory allocation failure corresponds to /art/runtime/gc/heap.cc, the following code

oid Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
  // If we're in a stack overflow, do not create a new exception. It would require running the
  // constructor, which will of course still be in a stack overflow.
  if (self->IsHandlingStackOverflow()) {
    self->SetException(
        Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
    return;
  }
  //....
  std::ostringstream oss;
  size_t total_bytes_free = GetFreeMemory();
  oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
      << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM,"
      << " target footprint " << target_footprint_.load(std::memory_order_relaxed)
      << ", growth limit "
      << growth_limit_;
  
  self->ThrowOutOfMemoryError(oss.str().c_str());
}

Through the above analysis, we also know that the system has a maximum memory constraint for each application, and OOM will occur if this value is exceeded. Let's demonstrate this type of OOM through a piece of code below

fun testOOM() {
    val manager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    Timber.e("app maxMemory ${manager.memoryClass} Mb")
    Timber.e("large app maxMemory ${manager.largeMemoryClass} Mb")
    Timber.e("current app maxMemory ${Runtime.getRuntime().maxMemory() / 1024 / 1024} Mb")
    var count = 0
    val list = mutableListOf<ByteArray>()
    while (true) {
        Timber.e("count $count    total ${count * 20}")
        list.add(ByteArray(1024 * 1024 * 20))
        count++
    }
}

In the above code, 20mb is applied for each time, and the test is divided into two cases,

  1. LargeHeap is not enabled:
 E  app maxMemory 256 Mb
 E  large app maxMemory 512 Mb
 E  current app maxMemory 256 Mb
 E  count 0    total 0
 E  count 1    total 20
 E  count 2    total 40
 E  count 3    total 60
 E  count 4    total 80
 E  count 5    total 100
 E  count 6    total 120
 E  count 7    total 140
 E  count 8    total 160
 E  count 9    total 180
 E  count 10    total 200
 E  count 11    total 220
 E  count 12    total 240
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 12386992 free bytes and 11MB until OOM, target footprint 268435456, growth limit 268435456
......
可以看到一共分配了 12次,在第十二次的时候抛出了异常,显示 分配 20 mb 失败,空闲只有 11 mb,
  1. Open largeHeap
app maxMemory 256 Mb                      
large app maxMemory 512 Mb
current app maxMemory 512 Mb
E  count 0    total 0
E  count 1    total 20
E  count 2    total 40
E  count 3    total 60
E  count 4    total 80
E  count 5    total 100
E  count 6    total 120
E  count 7    total 140
E  count 8    total 160
E  count 9    total 180
E  count 10    total 200
E  count 11    total 220
E  count 12    total 240
E  count 13    total 260
E  count 14    total 280
E  count 15    total 300
E  count 16    total 320
E  count 17    total 340
E  count 18    total 360
E  count 19    total 380
E  count 20    total 400
E  count 21    total 420
E  count 22    total 440
E  count 23    total 460
E  count 24    total 480
E  count 25    total 500
FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 31874
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 8127816 free bytes and 7937KB until OOM, target footprint 536870912, growth limit 536870912
可以看到分配了25 次,可使用的内存也增加到了 512 mb

Failed to create thread

Thread creation consumes a lot of memory resources. The creation process involves the java layer and the native layer, which is essentially completed in the native layer, corresponding to /art/runtime/thread.cc, as shown in the following code

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  //........
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

Here I borrow a photo from the Internet to see the process of creating a thread

According to the above figure, you can see that there are two main parts, namely creating JNI Env and creating threads

Failed to create JNI Env
  1. FD overflow causes JNIEnv creation to fail
E/art: ashmem_create_region failed for 'indirect ref table': Too many open files java.lang.OutOfMemoryError:Could not allocate JNI Env at java.lang.Thread.nativeCreate(Native Method) at java.lang.Thread.start(Thread.java:730)
  1. Insufficient virtual memory causes JNIEnv creation to fail
E OOM_TEST: create thread : 1104
W com.demo: Throwing OutOfMemoryError "Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log." (VmSize 2865432 kB)
E InputEventSender: Exception dispatching finished signal.
E MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
MessageQueue-JNI: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E MessageQueue-JNI:      at java.lang.Thread.nativeCreate(Native Method)
E MessageQueue-JNI:      at java.lang.Thread.start(Thread.java:887)

E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: Process: com.demo, PID: 3533
E AndroidRuntime: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E AndroidRuntime:        at java.lang.Thread.nativeCreate(Native Method)
E AndroidRuntime:        at java.lang.Thread.start(Thread.java:887)
Failed to create thread
  1. The virtual machine fails due to insufficient memory

    Native sets the thread size through FixStackSize

static size_t FixStackSize(size_t stack_size) {
  if (stack_size == 0) {
    stack_size = Runtime::Current()->GetDefaultStackSize();
  }
  stack_size += 1 * MB;
  if (kMemoryToolIsAvailable) {
    stack_size = std::max(2 * MB, stack_size);
  }  if (stack_size < PTHREAD_STACK_MIN) {
    stack_size = PTHREAD_STACK_MIN;
  }
  if (Runtime::Current()->ExplicitStackOverflowChecks()) {
    stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
  } else {
    stack_size += Thread::kStackOverflowImplicitCheckSize +
        GetStackOverflowReservedBytes(kRuntimeISA);
  }
  stack_size = RoundUp(stack_size, kPageSize);
  return stack_size;
}

W/libc: pthread_create failed: couldn't allocate 1073152-bytes mapped space: Out of memory
W/tch.crowdsourc: Throwing OutOfMemoryError with VmSize  4191668 kB "pthread_create (1040KB stack) failed: Try again"
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
        at java.lang.Thread.nativeCreate(Native Method)
        at java.lang.Thread.start(Thread.java:753)
  1. The number of threads exceeds the limit

    Test it out with a simple piece of code

fun testOOM() {
    var count = 0
    while (true) {
        val thread = Thread(Runnable {
            Thread.sleep(1000000000)
        })
        thread.start()
        count++
        Timber.e("current thread count $count")
    }
}
通过打印日志发现,一共创建了 2473 个线程,当然这些线程都是没有任务的线程,报错信息如下所示
pthread_create failed: couldn't allocate 1085440-bytes mapped space: Out of memory
Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again" (VmSize 4192344 kB)

FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 18085
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
	at java.lang.Thread.nativeCreate(Native Method)
通过测试可以看出来,具体的原因也是内存不足引起的,而不是线程数量超过限制,可能是测试的方法有问题,或者说是还没有达到最大线程的限制,由于手机没有权限,无法查看线程数量限制,所以等有机会了再看。

OOM monitoring

We all know that most of the reasons for the occurrence of OOM are due to memory leaks, which cause the memory to be unable to be released, so OOM occurs. Therefore, the monitoring mainly monitors memory leaks. Now the market is very mature in terms of memory leak inspection. Let's look at some commonly used monitoring methods

LeakCanary

It is very easy to use, you only need to add dependencies and you can use it directly. You can implement memory leak detection without manual initialization. When a memory leak occurs, a notification will be automatically sent, and you can click to view the specific leaked stack information.

LeakCannary can only be used in the debug environment, because it dumps the memory snapshot of the current process and freezes the current process for a period of time, so it is not suitable for use in the official environment.

Android Profile

You can visually view the memory usage in the form of images, and you can directly capture heap dump, or capture native memory (C/C++) and Java/Kotlin memory allocation. It can only be used offline, and the function is very powerful, but memory leaks, jitter, forced GC, etc.

ResourceCanary

ResourceCanary is a sub-module of Matrix, which exposes the hard-to-find Activity leaks and redundant Bitmaps that were created repeatedly, and provides information such as reference chains to help troubleshoot these problems

ResourceCanary separates detection and analysis, and the client is only responsible for detecting and dumping memory image files, and trims the Hprof files generated in the inspection part to remove most useless data. Bitmap object detection is also added to facilitate reducing memory consumption by reducing the number of redundant Bitmaps.

KOOM

Both of the above can only be used offline, while KOOM can be used online. KOOM is a complete solution developed by Kuaishou, which can realize leak monitoring of Java, native and thread

Optimization direction

image optimization

The memory occupied by the picture has nothing to do with the size of the picture. It mainly depends on the width and height of the picture and the loading method of the picture. For example, ARGB__8888 is twice as large as the memory occupied by RGB_565. To calculate how much the picture occupies Memory, here are several ways to optimize pictures

  • Unified Image Gallery

    In the project, you should avoid using multiple image libraries, which will lead to a series of problems such as repeated caching of images

  • Image Compression

    For some image resource files, you can compress the image when adding it to the project. Here is a plug-in recommended. CodeLocatorThis plug-in seems to be unusable a while ago. Here is a plug-in recommended McImageto compress the image when packaging. Compression processing.

    Use as follows

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.smallsoho.mobcase:McImage:1.5.1'
    }
}
然后在你想要压缩的Module的build.gradle中应用这个插件,注意如果你有多个Module,请在每个Module的build.gradle文件中apply插件
apply plugin: 'McImage'
最后将我代码中的mctools文件夹放到项目根目录
mctools
配置

你可以在build.gradle中配置插件的几个属性,如果不设置,所有的属性都使用默认值
McImageConfig {
        isCheckSize true //是否检测图片大小,默认为true
        optimizeType "Compress" //优化类型,可选"ConvertWebp","Compress",转换为webp或原图压缩,默认Compress,使用ConvertWep需要min sdk >= 18.但是压缩效果更好
        maxSize 1*1024*1024 //大图片阈值,default 1MB
        enableWhenDebug false //debug下是否可用,default true
        isCheckPixels true // 是否检测大像素图片,default true
        maxWidth 1000 //default 1000 如果开启图片宽高检查,默认的最大宽度
        maxHeight 1000 //default 1000 如果开启图片宽高检查,默认的最大高度
        whiteList = [ //默认为空,如果添加,对图片不进行任何处理

        ]
        mctoolsDir "$rootDir"
        isSupportAlphaWebp false  //是否支持带有透明度的webp,default false,带有透明图的图片会进行压缩
        multiThread true  //是否开启多线程处理图片,default true
        bigImageWhiteList = [
                "launch_bg.png"
        ] //默认为空,如果添加,大图检测将跳过这些图片
}
  • picture monitoring

    By monitoring all the pictures in the app, check the memory size occupied by the pictures in real time, so as to find out whether the picture size is appropriate, whether there is leakage, etc., recommend an analysis tool, which can obtain the number and size of pictures in the memory, and can AndroidBitmapMonitorget Bitmap creation stack, etc.

    In addition, monitoring can also be realized through customization. By monitoring setImageDrawable and other methods, the size of the picture and the size of the ImageView itself can be obtained, and then judge whether the prompt needs to be modified. The specific scheme is as follows:

    1. Customize ImageView, override setImageResource, setBackground and other methods, and detect the size of the image in it
    2. Using a custom ImageView, it is definitely unrealistic to replace them one by one. Here are two ways. The first is to modify the bytecode during compilation and replace all ImageViews with custom ImageViews. The second is to rewrite Layoutinflat .Fractory2, when creating a View, replace ImageVIew with a custom ImageView, which can be encapsulated and replaced with one click.
    3. Checking can be done asynchronously, or when the main thread is idle.
  • Optimize loading method

    Under normal circumstances, we use Glide or other image loading libraries to load images. For example, the default image loading format of Glide is ARGB_8888. For this format, each pixel needs to occupy four bytes. For some low-end computers Type, in order to reduce the memory occupation rate, you can modify the loading format of the image to RGB_565, compared with ARGB_8888, each pixel has only two bytes, so it can save half of the memory, the cost is less transparent channel, for those who do not need transparent channel For pictures, loading in this way is undoubtedly a better choice.

fun loadRGB565Image(context: Context, url: String?, imageView: ImageView) {
    Glide.with(context)
        .load(url)
        .apply(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
        .into(imageView)
}
也可以指定 Bitmap 的格式
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
  • Set image sampling rate
Options options = new BitmapFactory.Options();
options.inSampleSize = 5; // 原图的五分之一,设置为2则为二分之一
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                       R.id.myimage, options);
inSampleSize 值越大,图片质量越差,需谨慎设置

Memory Leak Optimization

If the memory is not released within the specified life cycle, it will be considered as a memory leak, and if there are too many memory leaks, it will occupy a large part of the memory, resulting in the new object not being able to apply for available memory. OOM will appear.

How to observe memory leaks The solution has been proposed above. Here we mainly talk about how to avoid some common memory leaks in the daily development process.

  1. memory thrashing

    Memory jitter mainly refers to the frequent creation of objects within a period of time, and the speed of recycling is not as fast as the speed of creation, and it leads to many discontinuous memory slices.

    Observed by Profiler, if GC occurs frequently and the memory curve is jagged, memory jitter is very likely to occur, as shown in the following figure:

​This situation must be caused by frequent creation of objects, for example: frequent creation of repeated objects in onDraw, constant creation of local variables in loops, etc. These are problems that can be directly avoided in development, and use Cache pool, manually release the objects in the cache pool, multiplex and so on.

2. Various common leakage methods

  • collection leak

  • singleton leak

  • Anonymous inner class leaks

The relationship between the static anonymous inner class and the outer class: if there is no parameter passed in, there is no reference relationship, the called does not need an instance of the outer class , the method and variable of the outer class cannot be called, and it has an independent life cycle

The relationship between the non-static anonymous inner class and the outer class automatically obtains a strong reference to the outer class. When called, an instance of the outer class is required, and methods and variables of the outer class can be called, depending on the outer class, and even longer than the outer class.

  • Leakage caused by unclosed resource files
    1. Main sequence broadcast
    2. Close input and output streams
    3. Recycle Bitmap
    4. Stop destroying animation
    5. Destroy WebView
    6. Log out eventBus and components that need to be logged out in time
  • Memory leak caused by Handler
如果 Handler 中有延时任务或者等待的任务队列过长,都有可能因为 Handler 的继续执行从而导致内存泄露

解决方法:静态内部类+弱引用

```java
private static class MyHalder extends Handler {

		private WeakReference<Activity> mWeakReference;

		public MyHalder(Activity activity) {
			mWeakReference = new WeakReference<Activity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//...
		}
	}
	最后在Activity退出时,移除所有信息
	移除信息后,Handler 将会跟Activity生命周期同步
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandler.removeCallbacksAndMessages(null);
	}
}
  • Multithreading leads to memory leaks

    1. Threads started using an anonymous inner class will hold a reference to the outer object by default
    2. If the number of threads exceeds the limit, it will cause a leak. In this case, the thread pool can be used.

Memory monitoring and back-and-forth strategies

Use the several methods described above to realize online/offline memory monitoring.

You can also start a thread by yourself to monitor the real-time memory usage at regular intervals. If the memory is in an emergency, you can clean up some caches that take up a lot of memory, such as Glide, to help.

Use the Activity's bottom-up strategy, do some View monitoring removal in BaseActivity's onDestory, set the background to null, and so on.

In order to help everyone better grasp the performance optimization in a comprehensive and clear manner, we have prepared relevant core notes (returning to the underlying logic):https://qr18.cn/FVlo89

Performance optimization core notes: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 at boot
  3. Start the SystemServer process at boot
  4. Binder driver
  5. AMS startup process
  6. The startup process of the PMS
  7. Launcher's startup process
  8. The four major components of Android
  9. Android system service - distribution process of Input event
  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/132474657
Recommended