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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
