1. What is OOM?
03-21 21:05:28.771: E/dalvikvm-heap(13316): Out of memory on a 10485776-byte allocation. 03-21 21:05:28.779: E/AndroidRuntime(13316): java.lang.OutOfMemoryError
These lines mean that our program needs to allocate 10485776 bytes, which is too large, and the virtual machine cannot satisfy us, so it shamefully shuts down and kills itself. This phenomenon usually occurs in the development of APPs that use many images or very large images. In simple terms, when our APP needs to apply for a piece of memory to load an image, the system thinks that the memory used by our APP is already too much. Even if it has 1G of free memory, it refuses to give more memory to our APP; then even though the system immediately throws an OOM error, the program does not catch the error, so a dialog box pops up and it crashes.
2. Why does OOM happen?
Because each process or each virtual machine of an Android system app has a maximum memory limit. If the applied memory resources exceed this limit, the system will throw an OOM error. It has little to do with the remaining memory of the entire device. For example, in earlier Android systems, a virtual machine had a maximum of 16M memory. After an app starts, the virtual machine keeps applying for memory resources to load images, and when it exceeds the memory upper limit, OOM occurs. How is the memory limit of Android system apps determined?
2.1 Composition of Android App memory:
App memory consists ofdalvik memoryandnative memoryIt consists of two parts. Dalvik is the Java heap; the objects created are allocated here, while native is memory allocated through c/c++. Bitmap is allocated in this way. (After Android 3.0, the system by default allocates Bitmap through Dalvik, and native is managed as a heap.) The sum of these two parts cannot exceed Android's memory limit for a single process or virtual machine.
What is the memory limit size of each phone?
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE) activityManager.getMemoryClass();
The above method returns a number in M (megabytes). The values vary on different system platforms or devices, such as HTC default 24M, Galaxy 36M, emulator-2.3 24M, etc. My Moto XT681 is 42M. 3
What is obtained above is the maximum memory resource of the virtual machine.
As for the size limit of the head heap, you can check the /system/build.prop file.
dalvik.vm.heapstartsize = 5m dalvik.vm.heapgrowthlimit = 48m dalvik.vm.heapsize = 256m
Note:The heapsize parameter indicates the maximum memory available for a single process heap. But if the following parameter "dalvik.vm.headgrowthlimit =48m" exists, it means that the heap memory of a single process is limited to 48m, that is, the program can actually only use 48m memory during its running process.
2.2 Why does the Android system set a memory limit for apps?
1 To make developers use memory more reasonably.Limiting the upper limit of available memory for each app can prevent certain applications from maliciously or unintentionally using too much memory, causing other applications to fail to run normally. Android is multi-process. If a process (that is, an application) consumes too much memory, other applications cannot run. Because of the limit, developers must make good use of limited resources and optimize resource usage.
2 The screen can display limited content; it is enough to have sufficient memory.Even if there are thousands of images and tens of millions of data items to use, what needs to be displayed to the user at a specific moment is always limited, because the screen display is just that big, and the information that can be placed on it is very limited. Most information is in a state of being ready to display, so there is no need to give too much heap memory. In other words, whenOOM phenomenon occurs, most of the reasons are that there are problems in our program design and optimization is needed.There are many optimization methods, such as trading time for space: continuously loading the images to be used, continuously recycling unused images, and decoding large images into images suitable for the phone screen size, etc.
3 The need for limiting multiple apps and multiple Dalvik virtual machines.Apps on Android use independent virtual machines; each time an app is opened, at least one independent virtual machine is started. This avoids a virtual machine crash causing the entire system to crash, at the cost of wasting more memory. This design ensures the stability of Android.
2.3 Doesn't GC automatically recycle resources? Why does OOM still occur?
Doesn't Android use GC to automatically recycle resources? Why aren't those unused resources in the app recycled?
Android's GC reclaims unused memory resources according to specific algorithms, preventing the app's memory requests from accumulating more and more. However, GC generally reclaims resources such as memory of unowned objects or soft-referenced resources, or even weaker reference resources. For example:
Bitmap bt = BitmapFactory.decodeResource( this .getResources(), R.drawable.splash); //此时的图片资源是强引用,是有主的资源。 bt = null ; //此时这个图片资源就是无主的了。gc心情号的时候就会去回收它。 SoftReference<Bitmap> softRef = new SoftReference<Bitmap>(bt); bt = null ; 其他代码...
When the program applies for a lot of memory resources, GC may release the image memory referenced by softRef. bt = softRef.get() may then return null, so the image needs to be reloaded.
Of course, this also illustrates the benefit of using soft references for image resources: GC will automatically release resources as needed, avoiding OOM to a certain extent.
TIPS: Develop the habit of setting unused objects to null in programming. In fact, it is even better to directly recycle unused images. Because letting GC reclaim through setting null may sometimes be too late.
2.4 How to check the memory allocation status of an app?
1. Monitor memory status through the heap tab in DDMS:
In the middle of the Heap view there is something called "data object", that is, data objects, which are class-type objects that exist in large numbers in our program.
In the row for data object, there is a column "Total Size", whose value is the total memory amount of all Java data objects in the current process. If there is a situation in the code where object references are not released, the "Total Size" value of data object will not show an obvious drop after each GC. As the number of operations increases, the "Total Size" value will become larger and larger, until it reaches an upper limit and the process is killed.
2. Inside the App, we can use totalMemory and freeMemory:
Runtime.getRuntime().freeMemory() RUntime.getRuntime().totalMemory()
3 adb shell dumpsys meminfo com.android.demo
3. Several points to note for avoiding OOM:
3.1 Appropriately adjust image size. Because the phone screen size is limited, the display area allocated to images is limited, especially for super-large images loaded from the network or SD card, where the image file size reaches several MB or more than a dozen MB:
Before loading into memory, first calculate the size of the bitmap, then appropriately adjust the sampling rate so that the loaded image is just right, or slightly larger and sufficient for display on the phone screen.
BimtapFactory.Option opts = new BitampFactory.Option();
opts.inJustDecodeBounds = true ;
opts.inSampleSize=computeSample(opts, minSideLength, maxNumOfPixels); // Android 提供了一种动态计算的方法 computeSampleSize
opts.inJustDecodeBounds = false ;
try {
return BitmapFactory.decodeFile(imageFile, opts);
} catch (OutOfMemoryError err){
}
3.2 Image caching. When loading a large number of images at once in controls such as ListView or Gallery, only load the resources displayed on the screen, do not load those not yet displayed, and promptly release resources that move off the screen. Use a two-level cache of strong reference + soft reference to improve loading performance. Cache images in memory using soft references, rather than reloading them into memory every time they are used.
3.3 Use encoding methods with lower memory usage. For example, Bitmap.Config.ARGB_4444 uses less memory than Bitmap.Config.ARGB_8888.
3.4 Recycle images in a timely manner. If a large number of Bitmap objects are referenced, and the app does not need to display all images at the same time, you can promptly recycle the Bitmap objects that are temporarily unused. For some scenarios where the usage status of images is clearly known, you can actively call recycle to reclaim them.
For the image resources on the app's startup splash screen, recycle them after use. For frame animations, you can load one, draw one, and release one.
3.5 Do not create too many local variables in loops. Use static with caution. When static is used to modify a member variable, the variable belongs to the class, not to an instance of the class, and its life cycle is very long. If it is used to reference instances that take up too much memory, you need to be cautious.
3.6 Customize heap memory allocation size. Optimize the heap memory allocation of the Dalvik virtual machine.
public class ClassName{
private static Context mContext;
// 省略
}
4. Several ways to avoid OOM when the App uses images:
4.1 Directly set to null or recycle
For a large number of images used in the app, adopt this approach: load them when used, and directly set to null or recycle them when not displayed.
This is a good habit and can basically eliminate OOM, but the drawback is that there is more code, and you may forget to recycle some resources.
In some cases, there may be inefficient things such as repeatedly loading, releasing, and reloading a specific image.
4.2 Simply manage image resources through SoftReference references
Create a HashMap of SoftReference
When using an image, first check whether the hashmap has a softreference and whether the image in the softreference is null.
If it is null, load the image into the softreference and add it to the hashmap.
There is no need to explicitly handle the recycling and releasing of images in the code; GC will automatically handle the release of resources.
This method is simple and practical to handle, and can avoid, to some extent, the inefficiency of repeatedly loading and releasing in the previous method. But it is not optimized enough.
4.3 Strong reference + soft reference two-level cache
The Android demonstration program ImageDownloader.java uses a two-level cache mechanism. That is, there is a data structure that directly holds references to successfully decoded Bitmap objects, while a second-level cache data structure holds soft reference objects of evicted Bitmaps. Due to the special nature of soft reference objects, when memory is needed, the system will first release the objects held by soft references. That is, when the VM finds that available memory is low and needs to trigger GC, the bitmap objects in the second-level cache will be recycled, while the bitmap objects held by the first-level cache are used for display.
In fact, the most critical point of this solution is using a relatively suitable data structure, namely LinkedHashMap type as the container for the first-level cache of Bitmaps. Due to the special nature of LinkedHashMap, we can control the number of objects stored in memory and remove unused objects from the container, putting them into the soft reference second-level cache. We can always keep the most recently accessed bitmap objects in the first-level cache. When the capacity of LinkedHashMap exceeds our preset value, the objects that have existed in the container for the longest time will be removed. At this time, we can put the removed objects from LinkedHashMap into the second-level cache container, and leave the management of objects in the second-level cache to the system. When the system needs to GC, it will first recycle the Bitmap objects in the second-level cache container.
When obtaining an image object, first search the first-level cache container. If there is a corresponding object and it is available, return it directly. If not, search the second-level cache for the corresponding SoftReference, determine whether the Bitmap held by the SoftReference object is available; if available, return it directly, otherwise return null. If the image cannot be found in the second-level cache either, then directly load the image resource.
4. LruCache + SD card cache method
5. Suggestions for two scenarios prone to OOM:
5.1 Downloading a large number of images over the network
For example, a Weibo client: multithreaded asynchronous network, small images directly use LRUCache+SoftRef+SD, large images are downloaded on demand:
5.2 For cases such as ListView, GridView that need to load a very large number of item entries
In the adapter's getView function, there is a convertView parameter that tells you whether there is a reusable view object. If convertView is not used, every call to getView will create a new view. This way, the previous view may not have been destroyed yet, and continuously creating new views will inevitably cause memory to surge, leading to OOM. In addition, when reusing convertView, the original images and other resources inside it become ownerless.
Here Google officially recommends using: "convertView + static class ViewHolder"
The official explanation is:
a. Reuse the cached convertView passed to the getView() method to avoid inflating unnecessary views.
b. Use the ViewHolder pattern to avoid unnecessary findViewById() calls; because too many findViewById() calls will also affect performance.
Additional explanation of the role of the ViewHolder class: The ViewHolder pattern stores a data structure in the tag of the view returned by the getView method. This data structure contains references to the views to which we want to bind data, thereby avoiding calling findViewById() every time getView() is called.
6. How to apply for memory allocation that exceeds the memory limit:
6.1 Allocate memory from Native C. Using NDK (Native Development Kit) and JNI, it can allocate memory at the C level (such as malloc/free or new/delete). Such allocation is not counted against the 24MB limit. This is true; allocating memory from native code is for Java's convenience, but it can be used to store data in RAM (even image data) to some extent.
6.2 Use OpenGL textures. Texture memory is not counted against the limit. To check how much memory your application has actually allocated, you can use android.os.Debug.getNativeHeapAllocatedSize(). With one of the Nexus devices using the above two techniques, I can easily allocate 300MB for a single foreground process — more than 10 times the default 24MB limit. From the above, using native code to allocate memory is not within the 24MB limit (OpenGL textures also use native code to allocate memory).
However, the risk with these two methods is that if the native heap allocates more memory than the system's available memory limit, it usually crashes directly.