One, foreword

As the project continues to grow, OOM (Out Of Memory) has become one Of the difficult problems on Bugly. Most business developers generally do not deal with OOM problems temporarily. On the one hand, it is because OOM problems do not have enough logs to be analyzed and solved in a short period Of time. No energy to research OOM solutions.

This article will take online OOM problems as a starting point to introduce the common OOM types, the principle of OOM occurrence, OOM optimization black technology, and the mainstream OOM monitoring scheme.

The article is longer, please prepare a small bench ~

OOM problem classification

Many people understand that the OOM is the Java VIRTUAL machine memory shortage, but through the analysis of OOM problems online, the OOM can be roughly classified into the following three categories:

  1. Too many threads
  2. Open too many files
  3. Out of memory

The following three problems will be analyzed respectively ~

Too many threads

3.1 Error Information is displayed

pthread_create (1040KB stack) failed: Out of memory

This is a typical OOM problem when creating a new thread

3.2 Source code Analysis

Pthread_create (Android 9) : androidxref.com/9.0.0_r3/xr…

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { ... pthread_create_result = pthread_create(...) If (pthread_create_result == 0) {return; } // Thread creation failed... { 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()); }}Copy the code

Pthread_create calls the Linux kernel to create a thread, so when does it fail to create a thread?

View the limit on the number of threads per process

cat /proc/sys/kernel/threads-max

The per-Max limits vary from device to device, and low-end models from some vendors are smaller and prone to these OOM issues.

View the number of threads in the current process

cat proc/{pid}/status

When the number of threads exceeds/proc/sys/kernel/threads-max“Will trigger the OOM.

Since the system has a limit on the number of threads per process, the key to solving this problem is to keep the peak number of threads as low as possible.

3.3 Thread Optimization

Looking back at an article I wrote two years ago, “Interviewer: Toutiao started quickly. What do you think might have been optimized?” “, can be regarded as my “peak” in nuggets (a bit narcissistic), although some of the content is outdated, but the analysis of the problem can be used for reference, remember at that time for thread optimization is just a sentence description, today this article just can do a supplement.

3.3.1 Disabling the new Thread

To solve the problem of excessive threads, the traditional solution is to prohibit the use of new threads and uniformly use Thread pools. However, it is generally difficult to be controlled manually. Automatic detection can be triggered after code submission, and corresponding developers will be notified by email if there is any problem.

But there are two problems with this approach:

  1. Can’t solve old codenew Thread;
  2. There is no control over third-party libraries.

3.3.2 Non-invasive optimization of new Thread

Thread of Java layer is just a common object. Native layer will be called to create Thread only after the start method is called.

Therefore, in theory, we can define a Thread by rewriting the start method. Instead of starting the Thread, we put the task into the Thread pool for execution. In order to make it non-invasive, we need to replace all the bytecodes of new threads with new custom threads during compilation by bytecode pegging.

Bytecode manipulation was described in detail in the previous article “ASM Hook Privacy method Calls to prevent App removal”, so there is no further explanation in this article.

The steps are as follows:

Create a Thread subclass called ShadowThread, override the start method, and call the CustomThreadPool CustomThreadPool to execute the task.

public class ShadowThread extends Thread { @Override public synchronized void start() { Log.i("ShadowThread", "start,name="+ getName()); CustomThreadPool.THREAD_POOL_EXECUTOR.execute(new MyRunnable(getName())); } class MyRunnable implements Runnable { String name; public MyRunnable(String name){ this.name = name; } @Override public void run() { try { ShadowThread.this.run(); Log.d("ShadowThread","run name="+name); } catch (Exception e) { Log.w("ShadowThread","name="+name+",exception:"+ e.getMessage()); RuntimeException exception = new RuntimeException("threadName="+name+",exception:"+ e.getMessage()); exception.setStackTrace(e.getStackTrace()); throw exception; }}}}Copy the code

2. At compile time, hook all the bytecodes of new threads and replace them with our custom shadowthreads. This should be easy and step by step.

We first confirm the Bytecode difference between new Thread and new ShadowThread. We can install an ASM Bytecode Viewer plug-in, as shown below

There are two differences:

NEW java/lang/Thread -> NEW com/lanshifu/oommonitor/ThreadTest$ShadowThread INVOKESPECIAL java/lang/Object.<init> ()V ->  INVOKESPECIAL com/lanshifu/oommonitor/ThreadTest$ShadowThread.<init> (Lcom/lanshifu/oommonitor/ThreadTest;) VCopy the code

The first is the owner of the modify method directive,

The second is to modify the method directive desc.

3. Since the task is executed in the thread pool, if the thread crashes, we don’t know which thread crashed. Therefore, the internal function of MyRunnable in the custom ShadowThread is to catch the exception, restore its name and throw another exception with more complete information when the thread has an exception.


The test code

    private fun testThreadCrash() {
        Thread {
            val i = 9 / 0
        }.apply {
            name = "testThreadCrash"
        }.start()
    }
Copy the code

Start a thread and trigger a crash with the following stack information:

As you can see, the original new Thread has been optimized into a CustomThreadPool call, and the crash will restore the Thread name without worrying about finding where the Thread was created.

Of course, there is a slight problem with this approach. If you want to collect information about all threads, the Thread name may not be accurate, because creating a Thread with a new Thread has been replaced by a Thread pool call, and the Thread name obtained is the name of the Thread in the pool

Data contrast

In the same scenario, we simply test the comparison of peak number of threads before and after new Thread optimization:

Peak number of threads (before optimization) Peak number of threads (after optimization) Reduce the maximum number of threads
337 314 23

The optimization effect is a little different for different apps, but you can see that the optimization actually works.

3.3.3 Non-intrusive thread pool optimization

As the project grew and more SDKS were introduced, most of them used their own thread pools internally to do asynchronous operations,

If the parameters of the thread pool are not set correctly, the core thread pool will not be released when it is idle, resulting in a high overall thread count.

Thread pool parameters:
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
Copy the code
  1. corePoolSize: Number of core threads. Core threads are not released by default, even if idle, unless setallowCoreThreadTimeOutTo true.
  2. MaximumPoolSize: indicates the maximum number of threads. When the number of tasks exceeds the number of core threads, the task will be put into the queue. When the queue is full, non-core threads will be started to execute the task. When the number of threads exceeds the limit, the policy will be rejected.
  3. KeepAliveTime: indicates the keepAliveTime of idle threads
  4. Unit: time unit
  5. WorkQueue: indicates a queue. When the number of tasks exceeds the number of core threads, tasks are placed in the queue until the queue is full, and a new thread is started to execute the first task in the queue.
  6. ThreadFactory: threadFactory. Implement the new Thread method to create a Thread
Using the thread pool parameter, we can find the following optimization points:
  1. Limit the idle thread lifetime,keepAliveTimeSmall Settings, such as 1-3s;
  2. Allows core threads to self-destruct when idle
executor.allowCoreThreadTimeOut(true)
Copy the code

How do you do that? In order to be non-invasive, ASM is still used to manipulate bytecode, which is basically the same as the replacement of new Thread

At compile time, using ASM, do the following:
  1. Will callExecutorsClass static methods are replaced with custom methodsShadowExecutorsStatic method, setexecutor.allowCoreThreadTimeOut(true);
  2. Will callThreadPoolExecutorClass constructor is replaced with customShadowThreadPoolExecutorStatic method, setexecutor.allowCoreThreadTimeOut(true);
  3. Our custom static methods can be invoked in < Clinit >() of the Application classShadowAsyncTask.optimizeAsyncTaskExecutor()To modify AsyncTask thread pool parameters, callexecutor.allowCoreThreadTimeOut(true);

Refer to Booster for details.

3.4 Thread Monitoring

If there are OOM creation problems after thread optimization, then we need to monitor if there are thread leaks.

3.4.1 Thread leak monitoring

There are several lifecycle methods that monitor native threads: pthread_create, pthread_detach, pthread_JOIN, and pthread_exit.

  1. Hook several methods, used to record the thread life cycle and stack, name and other information;
  2. When a Joinable thread executes pthread_exit without detach or join, the leaked thread is logged.
  3. When appropriate, the reporting thread leaks information.

For details, see oom -thread_holder

3.4.2 Thread Reporting

When abnormal threads are detected, thread information can be collected and reported to the background for analysis.

The code for collecting thread information is as follows:

private fun dumpThreadIfNeed() { val threadNames = runCatching { File("/proc/self/task").listFiles() } .getOrElse { return@getOrElse emptyArray() } ? .map { runCatching { File(it, "comm").readText() }.getOrElse { "failed to read $it/comm" } } ? .map { if (it.endsWith("\n")) it.substring(0, it.length - 1) else it } ? : emptyList() Log.d("TAG", "dumpThread = " + threadNames.joinToString(separator = ",")) }Copy the code


Here are the OOM problems caused by opening too many files

Opening too many files

4.1 Error Information

E/art: ashmem_create_region failed for 'indirect ref table': Too many open files
Java.lang.OutOfMemoryError: Could not allocate JNI Env
Copy the code

This problem is related to the system and the manufacturer

4.2 System Restrictions

Android is based on the Linux kernel. /proc/pid/limits describes the resource limits that Linux imposes on each process.

The figure below shows an Android 6.0 device with a Max Open files limit of 1024

If you do not have root permission, you can run the ulimit -n command to view Max Open files, and the result is the same

ulimit -n

On Linux, everything is a file, and every time a process opens a file, it generates a file descriptor fd (recorded under /proc/pid/fd).

cd /proc/10654/fd

ls

These fd files are linked files, and you can view their real file paths by using ls -L

When the number of FDS reaches the specified number of Max Open Files, a Too many Open Files crash is triggered, which is easier to reproduce on low-end machines.

How to optimize the file descriptor

4.2 File Descriptor Optimization

For the problem of too many open files, blind optimization is actually impossible to start, the overall plan is to monitor.

To view the fd information of the current process, run the following code

private fun dumpFd() { val fdNames = runCatching { File("/proc/self/fd").listFiles() } .getOrElse { return@getOrElse emptyArray() } ? .map { file -> runCatching { Os.readlink(file.path) }.getOrElse { "failed to read link ${file.path}" } } ? : emptyList() Log.d("TAG", "dumpFd: size=${fdNames.size},fdNames=$fdNames") }Copy the code

4.3 File Descriptor Monitoring

Monitoring policy: When the number of FDS exceeds 1000 or the number of FDS increases by more than 50, the system triggers THE collection of FDS and reports the file paths corresponding to FDS to the background.

Here, a bug is simulated, opening a file for several times without closing it. Through dumpFd, you can see many duplicate file names, and then roughly locate the problem.

When a file is suspected to have problems, we also need to know where the file was created and who created it. This involves IO monitoring ~

4.4 the IO monitor

4.4.1 Monitoring content

Monitors complete I/O operations, including open, read, write, and close

Open: get file name, fd, file size, stack, thread

Read /write: Obtains the file type, read/write count, total size, buffer size, and total read/write time

Close: indicates the total time for opening a file and the maximum continuous read/write time

4.4.2 Java Monitoring Scheme:

Taking Android 6.0 source code as an example, the call chain of FileInputStream is as follows

java : FileInputStream -> IoBridge.open -> Libcore.os.open ->  
 BlockGuardOs.open -> Posix.open
Copy the code

Libcore. Java is a good hook point

package libcore.io;
public final class Libcore {
    private Libcore() { }

    public static Os os = new BlockGuardOs(new Posix());
}

Copy the code

This Os variable is an interface type that defines the open, read, write, and close methods, which are implemented in BlockGuardOs.

// reflection gets the static variable Class<? > clibcore = Class.forName("libcore.io.Libcore"); Field fos = clibcore.getDeclaredField("os");Copy the code

Through the way of dynamic proxy, before and after all its IO methods add staking code to count IO information

// Dynamic Proxy object proxy.newProxyInstance (cposix.getClassLoader (), getAllInterfaces(cPosix), this); beforeInvoke(method, args, throwable); result = method.invoke(mPosixOs, args); afterInvoke(method, args, result);Copy the code

The disadvantages of this scheme are as follows:

  • Poor performance, frequent IO calls, the use of dynamic proxies and Java string operations, resulting in poor performance, can not meet the online standard
  • Inability to monitor Native code is also important
  • Poor compatibility: Need to adapt according to Android version, especially Android P’s non-public API restrictions

4.4.3 Native Monitoring scheme

The core of Native Hook scheme selects the target function of Hook from these functions in LIBc.so

int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t size);
ssize_t write(int fd, const void *buf, size_t size); write_cuk
int close(int fd);
Copy the code

So, libOpenjdkJVM. so, libopenjdkJVM. so to override all Java layer I/O calls.

Different versions of Android have different implementations, and after Android 7.0, we will need to replace these three methods.

open64
__read_chk
__write_chk
Copy the code

The native Hook framework currently widely used is IQiyi’s XHook and its improved version, Bytedance’s Bhook.

Specific native IO monitoring code, can refer to matrix-Iocanary, internal use is xhook framework.

IO involves a lot of knowledge, I can arrange a separate article later when I have time.


Let’s look at the last OOM type

Five, insufficient memory

5.1 Stack Information

This is the most common OOM, the Java heap is not enough memory, 512MB is not enough to play ~

Most of the devices where this problem occurs are Android 7.0, with higher versions available, but relatively few.

5.2 Revisit the JVM memory structure

At runtime, the JVM divides memory into the following five parts

  1. Method area: store static variables, constants, real-time compilation code;
  2. Program counter: thread private, a record of the number of lines of code currently executed, so that the CPU can not get lost when switching to another thread and back;
  3. Java virtual machine stack: Thread private, a Java method starts and ends, corresponding to a stack frame loading and unloading, the stack frame contains local variable table, operand stack, return address, symbol reference, etc.
  4. Native method stack: thread private, different from Java virtual machine stack in that this is for native methods;
  5. Heap: Most object creation allocates memory in the heap

In addition, large arrays and Bitmap pixel data from Android3.0 to 7.0 are stored in the heap.

The OOM problem caused by insufficient Java heap memory is difficult to repeat online, and it is often difficult to locate the problem. Most devices are below 8.0, mainly because Android 3.0-7.0 Bitmap pixel memory is stored in the heap.

Based on this conclusion, the optimization plan for OOM caused by insufficient Java heap memory mainly includes image loading optimization and memory leak monitoring.

5.3 Image loading optimization

5.3.1 Conventional picture optimization methods

Two years ago, I wrote an article “The interviewer: the resume is best not to write Glide, not ask the source code so simple”, the core content of the article is probably as follows:

  1. The advantages and disadvantages of Glide and Fresco as well as their application scenarios are analyzed.
  2. The problems to be considered in designing an image loading framework are analyzed.
  3. There are three ways to prevent images from taking up too much memory in OOM: soft reference, onLowMemory, Bitmap pixel location

This article is still somewhat meaningful now, the principle part of which has not been outdated, but the technology is updated and iterated, the conventional optimization method is not enough, long-term consideration, can do automatic compression of pictures, automatic detection of large pictures and alarms.

5.3.2 Non-invasive automatic compression of images

For picture resources, designers tend to pursue hd effects, ignoring the size of the picture, the general approach is to get the picture after manual compression, this manual operation completely depends on personal cultivation.

The mainstream solution is to use Gradle Task principle. During compilation, mergeResourcesTask mergeResourcesTask merges resources of aar and Module. We can get all resource files after mergeResourcesTask by:

  1. inmergeResourcesTaskAfter this Task, add a picture processing Task to get all the resource files;
  2. After getting all the resource files, determine if it is an image file, compress it through the compression tool. If the image becomes smaller after compression, replace the compressed image with the original one.

For details, see the McImage library.

5.4 Big picture Monitoring

5.4.1 Monitor from the picture frame

Many apps may use multiple image libraries, such as Glide, Picasso, Fresco, ImageLoader, Coil, etc. If we want to monitor an image frame, we need to read the source code and find the hook point.

Glide can be hooked to a requestListeners that we can create for ourselves.

Dokit-bigimgclasstransformer = dokit-bigimgClasstransFormer = dokit-bigimgClasstransFormer = dokit-bigimgClasstransFormer = dokit-bigimgClasstransFormer

5.4.2 Monitor from ImageView

5.4.1 Monitor the large picture from the picture loading frame side. If too many picture loading frames are used in the project, some third-party SDKS may carry out picture loading by themselves.

In this case, we can monitor the image from the ImageView control side. We can listen to setImageDrawable and other methods to calculate the image size. If the image size is larger than the control itself, the debug package can pop up a message indicating that it needs to be modified.

The scheme is as follows:

  1. Custom ImageView, rewriteSetImageDrawable, setImageBitmap, setImageResource, setBackground, setBackgroundResourceThese methods, within these methods, check the size of a Drawable;
  2. Compile time, modify bytecode, will allImageViewAre replaced with custom onesImageView;
  3. In order not to affect the main thread, can be usedIdleHandler, and then check when the main thread is idle.

The final hope is that when a large image is detected, the DEBUG environment can pop up to prompt the development to modify, and the release environment can report to the background.

Of course, this scheme has a drawback: you can’t get the image URL.

5.5 Memory Leak Monitoring Evolution

LeakCanary

As you probably know about memory leaks, just add a dependency

DebugImplementation ‘com. Squareup. Leakcanary: leakcanary – android: 2.8.1’,

You can automatically detect and analyze memory leaks and issue a notification to display memory leak details.

LeakCanary can only be used in the Debug environment as it is the current process that dumps the memory snapshot, debug.dumphProfData (path); The current process will be frozen for a period of time, and the entire APP will be stuck for about 5-15s, which may take tens of seconds on low-end phones.

ResourceCanary

Wechat has made some changes to LeakCanary to separate detection and analysis. The client is only responsible for detecting and dumping memory image files, which are then tailored and reported to the server for analysis.

See this article Matrix ResourceCanary — Activity leakage and Bitmap redundancy detection

KOOM

No matter LeakCanary or ResourceCanary, they can only be used offline. However, KOOM’s online memory leak monitoring solution is relatively complete. I will analyze the core process of the online memory leak monitoring solution based on KOOM.

5.6 Online Memory Leak Monitoring Schemes

Source code analysis based on KOOM

5.6.1 Trigger monitoring Time

  1. Check once every 5s
  2. Conditions for triggering memory mirroring collection:
  • When the memory usage exceeds 80%
//->OOMMonitorConfig private val DEFAULT_HEAP_THRESHOLD by lazy { val maxMem = Sizeunit.bytes.tomb (runtime.getruntime ().maxMemory()) when {maxMem >= 512-10 -> 0.8f maxMem >= 256-10 -> 0.85 F else }} - > 0.9 fCopy the code
  • During the two detection periods (for example, within 5s), the memory usage increases by 5%

5.6.2 Memory Image Collection

We know that LeakCanary detects a memory leak and cannot be used online because it dumps the memory image in the current process, freezing the App for some time.

Therefore, as an online OOM monitor, a separate process is required to dump memory images.

The overall strategy is:

Virtual machine supend->fork Virtual machine process -> Virtual machine resume-> Dump memory mirroring policy.

Dump image file dump image file dump

//->ForkJvmHeapDumper public boolean dump(String path) { ... boolean dumpRes = false; Int pid = suspendAndFork(); // select * from suspendAndFork(); MonitorLog.i(TAG, "suspendAndFork,pid="+pid); Debug.dumpHprofData(path); // If (pid == 0) {//2. exitProcess(); } else if (pid > 0) {// if (pid > 0) {dumpRes = resumeAndWait(pid); MonitorLog.i(TAG, "notify from pid " + pid); } } return dumpRes; }Copy the code

Note 1: The parent process calls native methods to suspend the virtual machine and create child processes. Note 2: If the subprocess is successfully created, run the debug. dumpHprofData command and exit the subprocess. Note 3: After the child process is successfully created, the parent process resumes the VM, unfreezes the VM, and the current thread waits for the child process to complete.

Note 1 the source code is as follows:

/ / - > native_bridge. CPP pid_t HprofDump: : SuspendAndFork () {/ / 1, to suspend the VM, If (android_API_ < __ANDROID_API_R__) {suspend_vm_fnc_(); }... Pid_t pid = fork(); if (pid == 0) { // Set timeout for child process alarm(60); prctl(PR_SET_NAME, "forked-dump-process"); } return pid; }Copy the code

Note 3 the source code is as follows:

CPP bool HprofDump::ResumeAndWait(pid_t pid) {//1. If (android_API_ < __ANDROID_API_R__) {resume_vm_fnc_(); }... int status; for (;;) If (waitpid(pid, &status, 0)! = -1 || errno ! = EINTR) {// The process exits unexpectedly if (! WIFEXITED(status)) { ALOGE("Child process %d exited with status %d, terminated by signal %d", pid, WEXITSTATUS(status), WTERMSIG(status)); return false; } return true; } return false; }}Copy the code

The main process can wait for the child process to finish the dump and then return to perform the memory image file analysis.

5.6.3 Memory Mirroring Analysis

The debug.dumphprofData (path) command is used to obtain the memory image file

//->HeapAnalysisService override fun onHandleIntent(intent: Intent?) {... BuildIndex (hprofFile)}.onfailure {it.printstacktrace (); MonitorLog.e(OOM_ANALYSIS_EXCEPTION_TAG, "build index exception " + it.message, true) resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, Json buildJson(intent) kotlin.runCatching {// There are several rules filterLeakingObjects()}.onFailure {monitorlog. I (OOM_ANALYSIS_EXCEPTION_TAG, "find leak objects exception " + it.message, true) resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, Null) return kotlin. RunCatching {// 4, whether gcRoot is reachable, FindPathsToGcRoot ()}.onFailure {it.printStackTrace() MonitorLog. I (OOM_ANALYSIS_EXCEPTION_TAG, "find gc path exception " + it.message, true) resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, Null) return} //5. FillJsonFile (jsonFile) resultReceiver?. Send (AnalysisReceiver.RESULT_CODE_OK, null) system.exit (0); }Copy the code

The process for memory mirroring analysis is as follows:

  1. throughsharkThis open source library converts hprof files intoHeapGraphobject
  2. Collect device information and package it into JSON. Onsite information is very important
  3. filterLeakingObjects: Filters out leaked objects. For example, activities that have destroyed and finished, fragments whose Fragment Manager is empty, Windows that have destroyed, etc.
  4. findPathsToGcRoot: Memory leak object, find it toGcRootThis is the step to figure out the cause of the memory leak
  5. fillJsonFile: formats the output of memory leak information

summary

Online Java memory leak monitoring scheme analysis, here is a summary:

  1. Suspends the current process and then passesforkCreate a child process;
  2. forkIt returns two times, one for the child process and one for the parent process. The returned PID can be used to determine whether the child process or the parent process.
  3. If the parent process returns, it passesresumeAndWaitResume the process, and then the current thread blocks and waits for the child process to finish;
  4. If the child process returns, passDebug.dumpHprofData(path)Read memory mirroring information, which is time-consuming, and exit the child process after the execution.
  5. The child exits, the parent exitsresumeAndWaitWill return, and you can start a service that analyzes memory leaks in the backgroundLeakCanaryThe principle of memory leak analysis is basically the same.

5.7 Native Memory leak monitoring

For Java memory leak monitoring, we can use LeakCanary offline and KOOM online, but how to monitor Native memory leak?

The scheme is as follows:

First, we need to understand the memory application functions of native layer: malloc, realloc, calloc, memalign, posix_memalign memory free function: free

  1. Hook A function that allocates and frees memory

When allocating memory, information such as stack, memory size, address, and thread is collected and stored in a map and removed from the map when releasing memory.

So how do you identify native memory leaks?

  • Periodic usemark-and-sweepAnalyze the whole process Native Heap to obtain the unreachable memory block information “address and size”.
  • Once we get the address of the unreachable memory block, we can get information about its stack, memory size, address, thread, and so on from our Map.

For details, see oom-native-leak

conclusion

This article starts from OOM problem

  1. Analyzing the OOM Type
  2. For the pthread_Create OOM problem, it can be non-invasivenewThreadOptimization, non-invasive thread pool optimization, and thread leak monitoring;
  3. For excessive file descriptors, the principle, file descriptor monitoring scheme and IO monitoring scheme are introduced.
  4. For OOM caused by insufficient Java memory, it introduces conventional picture loading optimization, two non-invasive big picture monitoring schemes, offline and online schemes for analyzing Java memory leak monitoring, and native memory leak monitoring schemes.

If you have any questions, leave them in the comments section, or find my contact information on github.

Source code of this article (later collation and supplement)

Reference article:

The incredible OOM

Wechat Android terminal memory optimization practice

Gradle plugin + ASM – Monitor images load alarms

booster

dokit-BigImgClassTransformer

KOOM

Matrix ResourceCanary – Activity leak and Bitmap redundancy detection

Android development master class