This article focuses on bones techniques for managing retentiveness usage in applications — such every bit web browsers, photo editors, and PDF viewers — that take big memory requirements.
First Some Theory
Most apps on Android run on top of Android Runtime (ART), which replaced the now deprecated Dalvik virtual car. Art and Dalvik are similar to Java Virtual Car (JVM) in that they share its underlying blueprint principles: They use two divide memory spaces to store application data — stack and heap.
Java stack memory is used to shop local variables (primitive types and references to objects). Each Java thread has its own split up stack. Stack retentiveness is relatively small compared to heap memory. The Java stack size on Dalvik is usually 32 KB for Java lawmaking and ane MB for native (C++/JNI) lawmaking. Art introduced a unified stack for both Java and C++ that is effectually 1 MB.
When an app hits the stack memory limit,
is emitted. The near probable cause for hit the stack limit is either infinite recursion or an excessively deep method call. Stack retentiveness is ever referenced in a LIFO (final in, first out) fashion. Whenever a method phone call is made, a new block (stack frame) with the method’due south local variables is pushed to the stack. When the method completes, its stack frame is popped from the stack and whatever possible result value is pushed dorsum onto the stack. Infinite recursion or an excessively deep call bureaucracy volition lead to hitting the stack size limit. The former problem is a bug that can be easily fixed, but the latter needs some refactoring in the course of unwrapping recursive method calls into a bicycle.
Java heap retentiveness is used by the virtual motorcar to allocate objects. Whenever you create an object, it’southward always created in the heap. Virtual machines, like JVM or ART, perform regular garbage collection (GC), making the heap memory of all objects that are non referenced anymore available for time to come allocations.
To provide a polish user experience, Android sets a hard limit on the heap size for each running awarding. The heap size limit varies amid devices and is based on how much RAM a device has. If your app hits this heap limit and tries to allocate more than retention, it will receive an
and will end. Then let’southward expect at some of the techniques for preventing this state of affairs.
Analyzing Heap Memory
The most important tool for investigating memory problems in your apps and for agreement how the memory is used is the Retentivity Profiler available in Android Studio.
This tool provides visualization for how much memory your app takes over time. Yous can take snapshots of the running app’south Java heap, tape retentiveness allocations, and inspect the heap or recorded memory allocations in a powerful UI.
Your typical retentiveness profiler session should look like this:
Look for frequent memory allocations and garbage collector runs to detect possible functioning issues.
Await for memory usage over time, especially after operations that are known to require a lot of memory allocation. Make sure that memory usage goes downwards after these operations are washed. For example, below you can see the memory impact of PSPDFKit’south
after loading a document.
Dump the heap at multiple points of your app runtime to inspect your memory usage. Look for large objects that are kept in memory and prevented from being garbage collected. Heap dumps can also help place memory leaks — for instance, you can search for your activities in the heap dump to run across if the sometime instances have been collected.
Here at PSPDFKit, we use the retentivity profiler extensively to spot memory usage issues while working with circuitous documents. Nosotros likewise use it regularly to go a quick idea of how large of a retentivity hit we are introducing with our new features or various refactorings (we always balance memory usage and performance).
Today’s garbage collectors are complex pieces of state-of-the-fine art applied science — the event of many years of research and development from endless people ranging from academics to development professionals. All the same, we still need to take intendance to avert introducing memory leaks.
The industry standard for detecting retentiveness leaks is the LeakCanary library. It automatically shows notifications when a retention leak is detected in your evolution builds, providing you with a stack trace of the leak in its UI. You lot can (and should) easily integrate it today!
Introducing new retentiveness leaks is particularly easy when working with circuitous lifecycles of Android activities or fragments. It’southward a mutual occurrence in places where developers concur strong references to UI
Contextdue south or other UI-specific objects in a background chore or in their static variables. Ane of the means to trigger these leaks is to rotate your device multiple times while testing your app.
Release Retentivity in Response to Events
Android tin reclaim memory of your app or outright kill your app when information technology’s necessary to free memory for more critical tasks. Earlier this happens, the arrangement gives you a chance to release any memory you don’t need. You’ll need to implement the
interface in your activity. And so, whenever the organization is under retentiveness force per unit area, you’ll receive a call to the
method and can release memory or disable features that won’t piece of work in such low-memory situations.
PSPDFKit too handles these callbacks. We designed PSPDFKit to utilize a lot of retention for caching so nosotros can make things more fluid. We don’t know how much retentiveness is available on a device, so PSPDFKit volition adapt and restrict usage when we become low-memory notifications. This makes it possible to run PSPDFKit-powered apps even on low-end devices, admitting with worse performance due to disabled caching.
One immediate solution for dealing with large retentivity requirements is to request a large Dalvik heap for your app. You can practise this past adding
belongings is set to
true, Android will create all processes for your application with a large heap. This setting is only intended for apps that tin’t work without it due to their nature, i.e. they are using large resources that need to fit into memory at the aforementioned time. It is strongly brash that you don’t use a large heap just to allow college memory usage. Y’all should always optimize your retentiveness usage, because a large heap on depression-memory, low-end devices can withal be as well small for your awarding.
Checking How Much Retentiveness Your App Can Use
Information technology’due south always a good thought to check how large the heap of your app is and dynamically arrange your code and available features to these memory limits. You can cheque the maximum heap size at runtime by using the methods
(when a big heap is enabled).
Android supports devices with as little as 512 MB of RAM. Make sure you remember depression-end devices too! You can cheque whether your app is running on a low-retentivity device via the
method. The exact beliefs of this method depends on the device, but it usually returns
on devices with less than 1 GB of RAM. You lot should make certain that your app works correctly on these devices and disable any features with big memory usage.
You tin can read more details almost how Android behaves on low-memory devices, too as some additional memory optimization tips for them, here.
Utilise Optimized Data Structures
In many cases, applications employ also much memory only considering they utilise suboptimal information structures.
Java collections can’t store efficient primitive types and require boxing for their keys or values. For instance,
with integer keys should be replaced with the optimized
SparseArray. Ultimately, y’all can ever utilize raw arrays instead of collections, which is a great idea when your collection does not resize.
Other retention-inefficient data structures include various serializations. While information technology’s true that XML or JSON formats are easy to use, using a more efficient binary format such as protocol buffers volition lead to lower memory usage.
These examples of retentivity-optimized data structures are meant only as a hint. As with all refactorings, y’all should first find the source of your bug before proceeding with these operation optimizations.
Prevent Memory Churn
Java/Android virtual machines allocate objects very quickly. Garbage drove is pretty fast too. Nonetheless, allocating a big number of objects in a small time piece could pb to a problem called retentivity churn. In such a case, the virtual automobile and garbage collector can’t sustain such frequent allocations and the application slows downwards or, in extreme cases, gets into an out-of-retentivity situation.
The main problem for us in Android land is that we take no control over when the GC is performed. This can potentially lead to functioning problems — for instance, if the GC runs during a UI animation and we miss the xvi ms threshold for smooth frame rendering. Thus it is important to foreclose excessive allocations in our lawmaking.
One example of a situation that leads to memory churn is allocating large objects such as
method of a view. This creates many objects quickly and could lead to garbage collection that tin negatively impact the performance of the view. You should always take care to monitor your memory usage to foreclose these situations.
Optimizing PSPDFKit’s Memory Usage
Here at PSPDFKit, we’ve built an SDK that tin can deal with very complex documents. Since rendering PDFs is a memory-consuming task, we demand to be extra careful in making sure apps using PSPDFKit won’t hit Android’southward memory limits, which will result in the app being killed past the OS.
We have an all-encompassing guide that lists almost of the tricks described in this article in the context of PSPDFKit-powered apps. If y’all are integrating PSPDFKit, I strongly recommend y’all take a look at information technology.
Random-admission memory (RAM) can exist rather constrained on mobile devices. Making sure that y’all are using it efficiently is especially important while building apps that work with bigger objects such as big bitmaps (PDF viewers, web browsers, photo editors) or big media files (audio or video editors). Tips from this article are a must for every developer who wishes to build quality apps with adequate behavior, even on low-memory devices.
I promise that you won’t meet whatsoever retentiveness-related crashes in your apps. Happy coding!