Mobile Development 12 min read

Analyzing and Solving OutOfMemoryError (OOM) in Meituan Android App

The Meituan Android team traced a surge of OutOfMemoryError crashes to duplicated drawable caches created by the AppCompat vector‑from‑resources flag, reproduced the condition on OPPO N1 devices, used heap‑dump analysis to pinpoint oversized byte‑array allocations, and eliminated the issue by disabling the flag, dramatically cutting OOM incidents.

Meituan Technology Team
Meituan Technology Team
Meituan Technology Team
Analyzing and Solving OutOfMemoryError (OOM) in Meituan Android App

In Android (Java) development, java.lang.OutOfMemoryError (OOM) is a common but difficult-to‑debug issue because its root cause is often not obvious and memory dump files are hard to obtain from end users, especially in production environments.

Case Background

During Meituan App versions 7.4 ~ 7.7, the food‑service module experienced a high number of OOM incidents, far exceeding historical levels. The OOMs were mainly caused by local DECODE resource errors. The chart below shows OOM counts for the first month after each release (including new and historical versions).

Approach

When OOM frequency surged in versions 7.6 ~ 7.7, the team hypothesized various causes (e.g., larger header images, different module loading strategies) but none matched the timing. Another hypothesis blamed certain ROM bugs, but lacked evidence. The fundamental way to locate the root cause is to identify which objects occupy the most memory and then analyze why.

Collecting User Device Memory Information

Directly dumping the full heap is impractical because dump files are large and uploading them from users is not feasible. An attempt was made to collect memory features at runtime and upload them with crash logs, using com.squareup.haha:haha:2.0.3 to analyze the dump. This approach failed for three reasons:

It required adding a new library.

Dumping and analyzing memory is time‑consuming.

When OOM occurs, memory is already exhausted; loading and analyzing a dump can trigger a second OOM.

Simulating OOM Reproduction

Since on‑device collection was infeasible, the team reproduced the OOM by simulating user scenarios. Because the exact user path is uncertain, the goal was to make the stack trace of the simulated OOM match the production OOM. The simulation required matching device parameters (memory size, resolution, OS version, etc.).

Mining OOM Features

Analysis of OOMs since version 7.4 identified common device characteristics: average memory, relatively high resolution, and similar stack logs. OPPO N1 devices accounted for about 65% of OOMs, so they were chosen for reproduction.

Key Data (Memory Dump)

The reproduced OOM was captured using Android Studio’s Android Monitor to dump an HPROF file, then converted with hprof-conv to a standard Java heap dump for analysis with Eclipse Memory Analyzer (MAT). In the histogram view, the largest objects were byte arrays. By inspecting the incoming references of these arrays, the following findings emerged:

These byte[] instances were owned by the EdgeEffect drawable. The drawable’s bitmap size (1566 × 406 × 4 = 2 543 184 bytes) matched the byte array size.

Another set of byte[] arrays belonged to a background image (720 × 200 × density = 1080 × 300 × 4 = 1 296 000 bytes), as shown below:

Inspecting the source code reveals how these drawables are loaded:

/**
 * Construct a new EdgeEffect with a theme appropriate for the provided context.
 * @param context Context used to provide theming and resource information for the EdgeEffect
 */
public EdgeEffect(Context context) {
    final Resources res = context.getResources();
    mEdge = res.getDrawable(R.drawable.overscroll_edge);
    mGlow = res.getDrawable(R.drawable.overscroll_glow);
    // ...
    mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
    mInterpolator = new DecelerateInterpolator();
}
for (int i = 0; i < N; i++) {
    int attr = a.getIndex(i);
    switch (attr) {
        case com.android.internal.R.styleable.View_background:
            background = a.getDrawable(attr); // TypedArray.getDrawable
            break;
        // ...
    }
}

Both Resources.getDrawable and TypedArray.getDrawable eventually call Resources.loadDrawable, which uses a cache. The issue turned out to be that multiple Resources instances were created, each with its own drawable cache.

By printing the Resources instances and their caches:

//noinspection unchecked
LongSparseArray<WeakReference<Drawable.ConstantState>> cache =
    (LongSparseArray<WeakReference<Drawable.ConstantState>>)
    Hack.into(Resources.class).field("mDrawableCache").get(getResources());
Object appCache = Hack.into(Resources.class).field("mDrawableCache").get(getApplication().getResources());
Log.e("oom", "Resources: {application=" + getApplication().getResources() + ", activity=" + getResources() + "}");
Log.e("oom", "Resources.mDrawableCache: {application=" + appCache + ", activity=" + cache + "}");

The logs showed that each Activity had its own Resources instance, explaining why the number of identical byte[] arrays grew proportionally with the number of started Activities.

The root cause was identified as the flag sCompatVectorFromResourcesEnabled (enabled via AppCompatDelegate.setCompatVectorFromResourcesEnabled ). Enabling this flag caused multiple Resources objects to be created, leading to duplicated drawable instances and increased memory consumption.

Disabling the flag in version 7.8 reduced OOM incidents dramatically: only 153 OOMs in the first month after release (including historical versions), with 22 occurring in the new version.

Summary

Thoroughly mine device and code features (memory size, resolution, code changes, timestamps, etc.) to generate hypotheses.

Reproduce OOMs via memory‑pressure testing (e.g., repeatedly loading complex pages) to obtain reliable heap dumps.

Analyze the dump with tools like MAT: sort instances by size, trace paths to GC roots, and identify duplicated resources.

The case demonstrates the importance of reading API documentation carefully, as seemingly harmless flags can have significant memory‑impact consequences.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Androidperformance analysisMeituanOutOfMemoryErrormemory dump
Meituan Technology Team
Written by

Meituan Technology Team

Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.