Android OOM problem and optimization summary

Proficiency in industry is more than diligence and playfulness, writing articles to practice expression skills, and writing codes to practice basic skills.

Summary of OOM and memory optimization

What is OOM?

OOM ( java.lang.OutOfMemoryError ), JVM does not have enough memory to allocate space for the object, exceeding the maximum heap space of jvm (-Xmx parameter), this exception will be triggered, resulting in the application being forcibly killed.

OOM reasons?

For java programmers, we generally just create objects, and we seldom worry about the recycling of objects, because the JVM has a garbage collector to perform GC regularly, and is responsible for recycling useless objects that have not been referenced to release memory. However, our application still has memory leaks or memory overflows, which are also two major factors that lead to oom.

Memory leak and memory overflow?

  • Memory leak refers to the previously allocated memory space, which is no longer used, but cannot be released by the garbage collector. If this situation keeps piling up, it will lead to memory overflow.
  • Out of memory, not enough memory to allocate space, not enough space.

In which area does OOM occur?

Let's first understand some jvm memory models:

insert image description here

According to the JVM specification, except for the program counter area, OOM may appear in other areas.

jvm memory allocation and recovery mechanism

We usually write java code, and most of the OOM occurs in the heap area, so we focus on understanding the memory model and recycling mechanism of heap space objects.

insert image description here

  • The heap space is divided into new generation (eden + S0 + S1), old generation (Tenured) and permanent generation (Permanent). The permanent generation java1.8 has been canceled and changed to metaspace.
  • Newly created objects are basically allocated in the new generation eden area, and large objects are directly allocated to the old generation.
  • After a GC, the surviving objects in the eden area are moved to S0; the surviving objects in S0 are moved to S1; the surviving objects in S1 are moved to Tenured
  • Every GC, the age of the object increases by one year, reaching 15 years old (default, configurable), and promoted to the old age.

The Judgment Algorithm for Object Recycling

  • Reachability analysis: Use GC Root as the starting point of the analysis to find references to objects. If an object is found that cannot be directly or indirectly referenced by GC Root, then the object can be recycled.

insert image description here

  • Reference counting method: Add a reference counter to the object. Whenever there is a reference to it, the counter value is increased by 1; when the reference becomes invalid, the counter value is decreased by 1.

Garbage Collection (GC)

Garbage collection algorithm :

  • mark-clear
  • mark-organize
  • copy

Garbage collection mainly works in the new generation and the old generation. In the generational collection model (above), different generations use different collection algorithms:

  • eden and Survivor: usually replication algorithms
  • Tenured: usually a markup algorithm, android is a CMS

Memory Limits for Android Apps

insert image description here

  • Java Heap: The memory requested by Java is limited by the size of vm.heapsize.

  • native Heap: The memory requested by the c/c++ layer is not limited by the size of vm.heapsize.

How to modify:

Code location/frameworks/base/core/jni/AndroidRuntime.cpp

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{

/*
     * The default starting and maximum size of the heap.  Larger
     * values should be specified in a product property override.
     */
    parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
    parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");

    parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
  
}

The source of Android throwing OOM

heap allocation failed

Code location: /art/runtime/gc/heap.cc

void 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()->GetPreAllocatedOutOfMemoryError());
    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";
  // If the allocation failed due to fragmentation, print out the largest continuous allocation.
  if (total_bytes_free >= byte_count) {
    space::AllocSpace* space = nullptr;
    if (allocator_type == kAllocatorTypeNonMoving) {
      space = non_moving_space_;
    } else if (allocator_type == kAllocatorTypeRosAlloc ||
               allocator_type == kAllocatorTypeDlMalloc) {
      space = main_space_;
    } else if (allocator_type == kAllocatorTypeBumpPointer ||
               allocator_type == kAllocatorTypeTLAB) {
      space = bump_pointer_space_;
    } else if (allocator_type == kAllocatorTypeRegion ||
               allocator_type == kAllocatorTypeRegionTLAB) {
      space = region_space_;
    }
    if (space != nullptr) {
      space->LogFragmentationAllocFailure(oss, byte_count);
    }
  }
  self->ThrowOutOfMemoryError(oss.str().c_str());
}

Commonly used memory analysis commands

adb shell dumpsys meminfo [package name]

insert image description here

adb shell procrank

Memory usage leaderboard, failed to try, need to upload sh by yourself.

adb shell cat /proc/meminfo

insert image description here

adb shell free

insert image description here

total = used + free

adb shell top

insert image description here


Memory Leak Analysis Tool

Memory Analyzer Tool

download link

1. Grab the memory application snapshot hprof file

adb shell am dumpheap com.xxx.xxx /data/local/tmp/14_54.hprof

2. Convert to standard hprof

hprof-conv 14_54.hprof 14_54_R.hprof

hprof-conv is in the platform-tools directory of the sdk

3. Use mat to open

insert image description here

The home page will have the current guess of memory leaks, large objects, and objects with a large number of objects will be set as suspect targets.

Analyzing memory leaks

Large object analysis

Click Overview - Dominator, the objects are sorted according to the size of the occupied space

insert image description here

Focus on checking the large objects listed above. In the figure below, it is obvious that there are multiple identical activities that keep referencing the large objects and cannot be released, resulting in memory leaks.
insert image description here

GC Root reference chain view

In the figure above, select an item, right-click -> Paths to GC Roots–>exclude all phantom/weak/soft etc reference, and focus on analyzing strong references.

insert image description here


You can see the context reference chain and locate it in your own code. In this example, a class statically holds the context of the Activity. After the Activity is destroyed, the context cannot be released, causing a memory leak.

Histogram comparison

If the problem is that the memory grows slowly over time, and a single hprof cannot see it, then take a snapshot again after a while, compare the two snapshot files, and see which object has been growing.

insert image description here

The hprof that analyzed the problem before could not be found, so I found two temporarily. The above picture is for reference only, and the actual operation shall prevail.


Android Profile

Open the Profile page, select the monitored program, and click the MEMORY–>dump button.

insert image description here

You can also view hprof on android studio for analysis.

insert image description here


LeakCanary

github address

quote

dependencies {
    
    
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

transfer

public class App extends Application {
    
    

    @Override
    public void onCreate() {
    
    
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
    
    
            return;
        }
        LeakCanary.install(this);
    }
}

example

public class MainActivity extends AppCompatActivity {
    
    

    private static int k = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(runnable).start();
    }


    public Runnable runnable = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            while (true){
    
    
                MainActivity.k++;
                Log.d("TAG",""+k);
            }

        }
    };

}

A memory leak occurs, go to this page to view it directly.
insert image description here
How to reduce the image size? cry


Memory leak, summary of common scenarios and solutions of memory overflow

The following excerpts are summarized by others and are here for backup.

1. The resource is not closed

When the resource object is no longer used, its close() function should be called immediately to close it, and then set it to null.
For example , unclosed resources such as Bitmap will cause memory leaks. At this time, we should close them in time when the Activity is destroyed.

2. The registration has not been cancelled.

For example, the memory leak caused by the unregistered BroadcastReceiver and EventBus, we should log out in time when the Activity is destroyed.

3. Static variables of the class hold large data objects

Try to avoid using static variables to store data, especially large data objects, it is recommended to use database storage.

4. Memory leak caused by singleton

Prioritize the use of the Context of the Application. If you want to use the Context of the Activity, you can use a weak reference to
encapsulate Then, get the Context from the weak reference where it is used. If you can’t get it, just return it directly. .

5. Static instances of non-static inner classes

The life cycle of this instance is as long as that of the application, which causes the static instance to always hold the reference of the Activity, and the memory resources of the Activity
cannot be recovered normally. If you need to use Context, try to use Application Context.

6. Handler temporary memory leak

After the Message is sent, it is stored in the MessageQueue. There is a target in the Message, which is a reference to the Handler
. If the Message exists in the Queue for too long, the Handler cannot be recycled. If the Handler is non-static,
the Activity or Service will not be recycled. And the message queue is constantly polling and processing messages in a Looper thread.
When the Activity exits, there are still unprocessed messages or messages being processed in the message queue, and the Message in the message queue
holds a reference to the Handler instance. The Handler also holds a reference to the Activity, so the memory resources of the Activity cannot be recovered in time
, causing a memory leak. The solution looks like this:

1. Use a static Handler internal class, and then use a weak reference to the object (usually Activity) held by the Handler, so that
when recycling, the object held by the Handler can also be recycled.
2. When the Activity is Destroyed or Stopped, the messages in the message queue should be removed to avoid
unprocessed messages in the message queue of the Looper thread that need to be processed.

It should be noted that AsyncTask is also a Handler mechanism, which also has the risk of memory leaks, but it is generally temporary. For
memory leaks like AsyncTask or threads, we can also separate the AsyncTask and Runnable classes or use static
inner classes.

7. Memory leaks caused by objects in the container not being cleaned up

Before exiting the program, clear the items in the collection, then set them to null, and then exit the program

8、WebView

WebView has the problem of memory leaks. As long as the WebView is used once in the application, the memory will not be released. We can
start an independent process for WebView, use AIDL to communicate with the main process of the application, and the process where WebView is located can
be destroyed at an appropriate time according to business needs, so as to achieve the purpose of releasing memory normally.

9. Memory leak caused by using ListView

When constructing the Adapter, use the cached convertView.

If there is an error, please help point out.

reference

OOM of Android memory optimization

User Guide - Android app performance optimization summary (memory performance optimization)

View your app's memory usage with the Memory Profiler

[Android memory optimization] memory jitter (garbage collection algorithm summary | generational collection algorithm supplement | memory jitter troubleshooting | memory jitter operation | collection selection)

JVM memory management: in-depth Java memory area and OOM

Guess you like

Origin blog.csdn.net/lucky_tom/article/details/113248541