OutOfMemoryError is a fatal exception that occurs when the Java Virtual Machine (JVM) or Android Runtime (ART) cannot allocate memory for a new object due to insufficient space in the Heap. According to Square Engineering, 70% of OutOfMemoryError in mobile applications are caused by memory leaks, not actual limit exceedance. Understanding the causes of OOM is key to application stability.
Key Takeaways
OutOfMemoryError (OOM) is an exception from the VirtualMachineError family in Java/Kotlin that signals the inability to allocate memory for a new object. Unlike checked exceptions, OOM is an Error and does not require handling through catch — although technically it can be caught. After an OOM occurs, the application is usually in an unstable state and it is recommended to terminate it.
On Android, each application has a Heap limit set by the device manufacturer. For modern smartphones with 6+ GB of RAM, the limit is 256–512 MB, for budget devices — 128–192 MB. When the total volume of all live objects exceeds this limit, ART throws OutOfMemoryError.
It is important to understand: OOM does not always mean that the device has run out of physical memory. It means that the application has exhausted its Heap limit set by the system. Other applications may have free memory, but your application cannot use it due to process isolation in Android.
Five scenarios regularly lead to OOM in mobile applications. Each scenario is associated with a specific data type or operation.
Bitmap is the main memory consumer in Android applications. Loading a FullHD image (1920 × 1080) at original size takes 8.3 MB in ARGB_8888 format. If there are 50 such images in a RecyclerView, that is 415 MB, exceeding the Heap of any device. Loading images without inSampleSize guarantees OOM on weak devices.
Use Glide or Coil for automatic scaling. These libraries load images at sizes matching the View rather than the original resolution. For direct use of BitmapFactory.Options, apply inSampleSize: calculate it as a power of two so that the final size does not exceed 2048 × 2048 pixels. Additionally, use RGB_565 instead of ARGB_8888 for images without transparency — this halves memory consumption.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
A single leak of a few KB will not cause OOM. But dozens of leaks on each screen accumulate: every screen transition adds a leak, GC cannot free objects, and the Heap fills up. A typical pattern: the user opens and closes the profile screen 20 times → Heap grows by 200 MB → the application crashes with OOM.
Install LeakCanary in the project for automatic leak detection. It will show each leaked object with an exact stack trace. After fixing all leaks, Heap consumption becomes stable: after closing a screen, memory returns to the baseline level.
Loading entire files into byte[] is a direct path to OOM. A 50 MB JSON file during parsing will create a string of the same size plus a DOM model. Video files loaded into memory, audio buffers, and large protobuf datasets — all of them can exceed the Heap limit in a single operation.
Process large data using streams: InputStream with a 4–8 KB buffer, Streaming JSON parser (Jackson or Gson with JsonReader), MediaCodec for video. Never call File.readBytes() on files larger than 10% of the available Heap.
Intensive object creation in a loop without intermediate GC can lead to OOM, especially on devices with a small Heap. Example: generating 100,000 objects in a for-loop that do not fit in the Heap before GC can collect them. This is more common in games and graphics editors.
Use Object Pool for objects that are created and destroyed en masse. For numerical data, use primitives (FloatArray instead of List<Float>). RecyclerView with ViewHolder Pool solves this problem for UI components.
Fragmentation is a state where there is enough free memory in total, but no contiguous block for a new object. ART compacts the Heap during GC, but not always successfully. Large arrays (Bitmap, byte[]) are most sensitive to fragmentation.
ART on Android 8+ uses Generational GC, which reduces fragmentation by separating young and old objects. Nevertheless, avoid allocating fragments of different sizes in the same pool — try to use pre-allocated fixed-size buffers instead.
The Heap limit in Android is not a constant — it depends on the manufacturer, device model, and OS version. Google sets minimum requirements through the Compatibility Definition Document (CDD), but manufacturers set the actual values.
| Device Category | Typical Heap | largeHeap |
|---|---|---|
| Budget (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Mid-range (3–4 GB RAM) | 256–384 MB | 512 MB |
| Flagship (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tablets (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | N/A |
You can request an increased limit through android:largeHeap="true" in the manifest. Use it with caution: increasing the Heap does not solve the leak problem and can worsen the user experience if the system is forced to kill other applications to free memory for yours. For Wear OS, the Heap limit is minimal — only 32–64 MB, largeHeap is not available here, and memory saving is doubly critical.
Diagnosing OOM requires analyzing a Heap Dump and understanding which objects consume memory. Android Studio provides all the necessary tools.
Step 1: Capture the OOM moment. In Android Memory Profiler, click Record memory allocations and perform the scenario that causes the crash. The Profiler will show a spike in allocations before OOM. If OOM is not reproducible, reduce the Heap via android:smallHeap in the debug build or use DDMS with manual GC invocation.
Step 2: Take a Heap Dump at peak load (before OOM). Open the Dump in Android Studio: the Classes tab is sorted by Retained Size. The largest objects are Bitmap, byte[], String. For each Bitmap, check the size (width × height × 4 bytes) and the load path via Stack Trace.
Step 3: Analyze the number of duplicate objects. If you see 200 identical Fragment or Activity instances — that is a leak. If 500 Bitmaps with the same size — it is an image caching problem. MAT (Memory Analyzer Tool) provides deeper analysis with a Dominator Tree showing which objects hold 80% of the Heap.
// Heap Dump command via adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
A comprehensive OOM prevention strategy includes five levels of protection: from architectural decisions to production monitoring.
ViewModel + Repository pattern separates data from UI and prevents View retention on screen rotation. ViewModel outlives the Activity, its data is not lost, and the View can be recreated without duplicating data in memory. Use StateFlow instead of LiveData for explicit state management.
Glide is a mandatory library for working with images. It automatically scales, caches (disk + memory), and recycles Bitmap. Configure diskCacheStrategy and skipMemoryCache for large lists. For animated images, use Glide with GIF/WebP — they take up less memory than a sequence of Bitmaps.
Firebase Performance Monitoring tracks memory consumption in real time. Set an alert on Heap usage exceeding 80% of the limit — this is a signal to investigate. Crashlytics collects OOM as an exception and shows the last known Heap state before the crash. For Android 11+, use ApplicationExitInfo for detecting OOM terminations.
Be sure to test the application on devices with minimal Heap (128–192 MB). An emulator with a small screen and small Heap emulates a budget device. If the application works on such a device, there will be no OOM problems on flagships. Use Firebase Test Lab with real devices from different price categories.
// Checking available Heap before heavy operation
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // 50% buffer
}
Frequently Asked Questions
Technically yes, but this is not recommended. After OOM, the application is in an unstable state: new allocations may fail, and some objects may be partially created. The only reasonable action in catch is logging and restarting the Activity.
The Heap limit varies across devices. An operation that requires 300 MB will crash on a device with a 192 MB limit but will succeed on a flagship with 512 MB. Test on devices with minimal specifications to detect OOM scenarios.
largeHeap increases the limit but does not speed up the application. GC pauses become longer since collecting a large Heap takes more time. The system may kill background applications to provide memory. Use largeHeap only for applications that objectively need a lot of memory (cameras, editors).
OOM is an exception inside an application when the Heap is insufficient. A system kill (Low Memory Killer) is a Linux kernel decision to kill a process to free memory for other applications. In a system kill, the application does not receive an exception — the process simply terminates.
The formula: width × height × bytesPerPixel. ARGB_8888 = 4 B/pixel, RGB_565 = 2 B/pixel. A FullHD Bitmap (1920 × 1080) in ARGB_8888 = 8.3 MB. A 4K Bitmap (3840 × 2160) = 33 MB. Always scale images to the size required for display on the screen.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also