Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OutOfMemoryError although vm has enough free memory

I am getting this weird OutOfMemoryError that occurs although the dalvikvm reports enough heap space. Logs:

12-09 14:16:05.527: D/dalvikvm(10040): GC_FOR_ALLOC freed 551K, 21% free 38000K/47687K, paused 173ms, total 173ms
12-09 14:16:05.527: I/dalvikvm-heap(10040): Grow heap (frag case) to 38.369MB for 858416-byte allocation
12-09 14:16:05.699: D/dalvikvm(10040): GC_FOR_ALLOC freed 6K, 21% free 38832K/48583K, paused 169ms, total 169ms
12-09 14:16:05.894: D/dalvikvm(10040): GC_FOR_ALLOC freed 103K, 20% free 38929K/48583K, paused 169ms, total 169ms
12-09 14:16:05.894: I/dalvikvm-heap(10040): Forcing collection of SoftReferences for 858416-byte allocation
12-09 14:16:06.074: D/dalvikvm(10040): GC_BEFORE_OOM freed 6K, 20% free 38922K/48583K, paused 182ms, total 182ms
12-09 14:16:06.074: E/dalvikvm-heap(10040): Out of memory on a 858416-byte allocation.
12-09 14:16:06.074: I/dalvikvm(10040): "AsyncTask #2" prio=5 tid=17 RUNNABLE
12-09 14:16:06.074: I/dalvikvm(10040):   | group="main" sCount=0 dsCount=0 obj=0x42013580 self=0x5f2a48d8
12-09 14:16:06.074: I/dalvikvm(10040):   | sysTid=10101 nice=10 sched=0/0 cgrp=apps/bg_non_interactive handle=1591062136
12-09 14:16:06.074: I/dalvikvm(10040):   | schedstat=( 7305663992 4216491759 5326 ) utm=697 stm=32 core=1
12-09 14:16:06.074: I/dalvikvm(10040):   at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
12-09 14:16:06.074: I/dalvikvm(10040):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:619)
12-09 14:16:06.074: I/dalvikvm(10040):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:691)

As you can see right before the outofmemory occurs the dalvikvm reports about 10mb free memory after gc. The allocation is for a 800k bitmap. I doubt that there is a race condition between gc and bitmap decoding here, because the reported free memory of dalvik didn't drop below 8mb free memory in all log statements of the last 20-30 seconds before the crash.

The problem occurs on a Samsung Galaxy Tab 2 10.1 running Android 4.1.2. I'm using a modified version of the ImageFetcher classes from the Google I/O app (2012), so I'm already doing stuff like inJustDecodeBounds when loading images to optimize sampleSize option.

As per documentation in Managing Bitmap Memory Android allocates Bitmap pixel data in the dalvik heap (since Android 3.0), so why does decoding the bitmap cause an outofmemory with 10mb free memory?

Has anyone seen this before or may have an idea what's happening?

EDIT: Per request here is the image loading code from the Google I/O app 2012. In my app I am just calling

mImageFetcher.loadImage(myUrl, myImageView);

EDIT2: The relevant image decoding methods extracted from above link to make clear that I am already using sample size optimizations:

public static Bitmap decodeSampledBitmapFromDescriptor(
        FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth,
            reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory
            .decodeFileDescriptor(fileDescriptor, null, options);
}

public static int calculateInSampleSize(BitmapFactory.Options options,
        int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will
        // guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

        // This offers some additional logic in case the image has a strange
        // aspect ratio. For example, a panorama may have a much larger
        // width than height. In these cases the total pixels might still
        // end up being too large to fit comfortably in memory, so we should
        // be more aggressive with sample down the image (=larger
        // inSampleSize).

        final float totalPixels = width * height;

        // Anything more than 2x the requested pixels we'll sample down
        // further.
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;

        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++;
        }
    }
    return inSampleSize;
}
like image 875
ct_rob Avatar asked Dec 09 '13 13:12

ct_rob


2 Answers

About OutOfMemory (OOM) on ~850000 bytes allocation when you have 10 Mb free, that is most certainly due to memory fragmentation there is no guarantee that the heap has one continous chunk of memory bigger than 850000 bytes and this is why you get the OOM.

Seems strange that you still get the error, you seem to have done some optimizations already, do you really release all memory you hold? I mean you have 38 Mb used heap, what are contained in that memory?

Have you tried looking at image loading libraries, such as, for instance picasso ?

Where one could such things as: Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").fit().into(imageView);

(This downloads and caches the image and fits and draws into an imageView, neat!)

Update

  1. You should analyze your hprof file (should be available on root sdcard path since you got an OOM) in MAT to see if your holding on to unnecessary references
  2. Release those references (null them out to let the GC collect them)
  3. Use inBitmap to reuse memory (became more powerful in KitKat, images does not need to be of same size as the previous image, just hold as much or less memory than previous)
  4. If you frequently ask for same image consider caching with e.g. LruCache
  5. If they are really big bitmaps try tile the image (will load small square bitmaps into a big image) look at: TileImageView (this is using GlView to draw though..)
like image 69
Magnus Avatar answered Oct 12 '22 13:10

Magnus


It seems like ICS and later Android versions are not letting your VM go to the total size of the heap. I've seen the same thing in my app,

You can add

android:largeHeap="true" 

to your <application> which gives your app a much larger heap. Not nice but works...

like image 41
Michael Avatar answered Oct 12 '22 13:10

Michael