Glide: OutOfMemory.

Created on 27 Dec 2014  ·  8Comments  ·  Source: bumptech/glide

I have a recyclerivew which hold a list view pagers, each viewPager has three pictures.and each picture's size is about 200kb.When load image from the network, the OOM occurs.And if you need, i will send you the 2 hprof file for comparison.I thought if there is something wrong with DownSampler when decodeStream.

803 31961100    byte[]  29  dalvik.system.VMRuntime newNonMovableArray  
647 31961100    byte[]  32  dalvik.system.VMRuntime newNonMovableArray  
316 31961100    byte[]  31  dalvik.system.VMRuntime newNonMovableArray  
289 31961100    byte[]  30  dalvik.system.VMRuntime newNonMovableArray  
279 31961100    byte[]  32  dalvik.system.VMRuntime newNonMovableArray  
144 31961100    byte[]  29  dalvik.system.VMRuntime newNonMovableArray  
8   31961100    byte[]  30  dalvik.system.VMRuntime newNonMovableArray  
203 3203859 byte[]  6   org.apache.harmony.dalvik.ddmc.DdmVmInternal    getRecentAllocations    
502 3167547 byte[]  6   org.apache.harmony.dalvik.ddmc.DdmVmInternal    getRecentAllocations    
177 2764812 byte[]  32  dalvik.system.VMRuntime newNonMovableArray  
66  2764812 byte[]  29  dalvik.system.VMRuntime newNonMovableArray  
1000    65548   byte[]  29  com.bumptech.glide.util.ByteArrayPool   getBytes    
999 65548   byte[]  29  com.bumptech.glide.util.ByteArrayPool   getBytes    
662 65548   byte[]  32  com.bumptech.glide.util.ByteArrayPool   getBytes    
661 65548   byte[]  32  com.bumptech.glide.util.ByteArrayPool   getBytes    
380 65548   byte[]  30  com.bumptech.glide.util.ByteArrayPool   getBytes    
339 65548   byte[]  31  com.bumptech.glide.util.ByteArrayPool   getBytes    
338 65548   byte[]  30  com.bumptech.glide.util.ByteArrayPool   getBytes    
329 65548   byte[]  31  com.bumptech.glide.util.ByteArrayPool   getBytes    

  at dalvik.system.VMRuntime.newNonMovableArray(Native Method)  
  at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)   
  at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)    
  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)    
  at com.bumptech.glide.load.resource.bitmap.Downsampler.decodeStream(Downsampler.java:307) 
  at com.bumptech.glide.load.resource.bitmap.Downsampler.downsampleWithSize(Downsampler.java:202)   
  at com.bumptech.glide.load.resource.bitmap.Downsampler.decode(Downsampler.java:140)   
  at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:50)    
  at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:19)    
  at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:39)    
  at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:20)    
  at com.bumptech.glide.load.engine.DecodeJob.decodeFromSourceData(DecodeJob.java:189)  
  at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:176)  
  at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:127)  
  at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)    
  at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)  


And the log:
12-27 14:49:42.071     601-1008/com.ganlan.poster I/art﹕ Alloc sticky concurrent mark sweep GC freed 7(192B) AllocSpace objects, 0(0B) LOS objects, 4% free, 170MB/178MB, paused 728us total 6.889ms
12-27 14:49:42.084     601-1008/com.ganlan.poster I/art﹕ Alloc partial concurrent mark sweep GC freed 99(4KB) AllocSpace objects, 0(0B) LOS objects, 8% free, 170MB/186MB, paused 609us total 11.547ms
12-27 14:49:42.117     601-1008/com.ganlan.poster I/art﹕ Alloc concurrent mark sweep GC freed 9(12KB) AllocSpace objects, 0(0B) LOS objects, 8% free, 170MB/186MB, paused 829us total 31.672ms
12-27 14:49:42.118     601-1008/com.ganlan.poster I/art﹕ Forcing collection of SoftReferences for 30MB allocation
12-27 14:49:42.137     601-1008/com.ganlan.poster I/art﹕ Alloc concurrent mark sweep GC freed 11(344B) AllocSpace objects, 0(0B) LOS objects, 8% free, 170MB/186MB, paused 524us total 18.585ms
12-27 14:49:42.138     601-1008/com.ganlan.poster E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 31961100 byte allocation with 16777120 free bytes and 21MB until OOM"
12-27 14:49:52.896      601-601/com.ganlan.poster D/poster_PosterCursorAda﹕ image :http://ganlan.qiniudn.com/21de4b26453e355c865cb97765d5d880
12-27 14:49:52.897      601-601/com.ganlan.poster D/poster_PosterCursorAda﹕ image : http://ganlan.qiniudn.com/21de4b26453e355c865cb97765d5d880
12-27 14:49:53.146      601-601/com.ganlan.poster D/poster_PosterCursorAda﹕ image :http://ganlan.qiniudn.com/21de4b26453e355c865cb97765d5d880
12-27 14:49:53.147      601-601/com.ganlan.poster D/poster_PosterCursorAda﹕ image : http://ganlan.qiniudn.com/21de4b26453e355c865cb97765d5d880
12-27 14:50:45.005     601-1009/com.ganlan.poster I/art﹕ Alloc sticky concurrent mark sweep GC freed 1733(133KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 184MB/186MB, paused 630us total 38.637ms
12-27 14:50:45.056     601-1009/com.ganlan.poster I/art﹕ Alloc partial concurrent mark sweep GC freed 255(9KB) AllocSpace objects, 5(152MB) LOS objects, 33% free, 31MB/47MB, paused 627us total 47.905ms
question

Most helpful comment

In Android, as with most frameworks, Bitmaps are uncompressed. As a result, the size they take in memory is based on the number of pixels in the image and the format of the Bitmap.

Glide uses one of two formats, RGB_565 and ARGB_8888. Glide prefers RGB_565, but since it doesn't support transparency, falls back to ARGB_8888 for certain images (and/or via a setting).

RGB_565 is 2 bytes per pixel, so 2000 * 3000 * 2 = 12000000 or 12mb. ARGB_8888 is 4 bytes per pixel, so 2000 * 3000 * 4 = 24000000 or 24mb. Since your images are somewhat larger, with the ARGB_8888 format, 30mb is about what we'd expect.

Since images of 2k by 3k are typically much larger than the view they would be displayed in, downsampling can help reduce the memory usage by only including a subset (1/2, 1/4, 1/8 etc) of the pixels in the Bitmap. If we don't downsample appropriately, or we can't because the view is sufficiently large, you will end up with a very large Bitmap. Decreasing the view size or using override (or pulling forward past the first for #288) will make it easier for Glide to downsample the images and should reduce the memory used. You could also try fitCenter() or centerCrop() to scale the images down to exactly your view size.

All 8 comments

31961100 is a 31mb Bitmap allocation. What are the dimensions of these images? Can you add your Glide.load line?

Usually OOMs indicate either a memory leak or an attempt to load an overly large image or images. There have been memory leaks in Glide in the past, but typically the leak is in the application itself. Do the heap dumps show a memory leak?

The image dimensions is 2448x3264, and the image size is about 200kb,the app got an OOM error while loading three or four image at the same time.Then i got some other images for test, the dimension is about 510x700, and the size is about 200kb and it worked.The heap dumps did't show memory leak.I guess maybe the image dimension is too much.So i'll send you the screenshot and hprof file.And the code is as below:

/**
 * Load an image from a url into an ImageView using the default placeholder
 * drawable if available.
 * @param url The web URL of an image.
 * @param imageView The target ImageView to load the image into.
 * @param requestListener A listener to monitor the request result.
 * @param placeholderOverride A drawable to use as a placeholder for this specific image.
 *                            If this parameter is present, {@link #mPlaceHolderResId}
 *                            if ignored for this request.
 */
public void loadImage(String url, ImageView imageView, RequestListener<String, Bitmap> requestListener,
            Drawable placeholderOverride, boolean crop) {
    BitmapRequestBuilder request = beginImageLoad(url, requestListener, crop)
            .animate(R.anim.image_fade_in);
    if (placeholderOverride != null) {
        request.placeholder(placeholderOverride);
    } else if (mPlaceHolderResId != -1) {
        request.placeholder(mPlaceHolderResId);
    }
    request.into(imageView);
}

public BitmapRequestBuilder beginImageLoad(String url,
        RequestListener<String, Bitmap> requestListener, boolean crop) {
    return requestManager.load(url)
            .asBitmap()
            .listener(requestListener)
            .transform(crop ? mCenterCrop : mFitCenter);
}

/**
 * Load an image from a url into the given image view using the default placeholder if
 * available.
 * @param url The web URL of an image.
 * @param imageView The target ImageView to load the image into.
 */
public void loadImage(String url, ImageView imageView) {
    loadImage(url, imageView, false /*crop*/);
}
public class PosterPagerAdapter extends PagerAdapter {
    final String[] posters;

    public PosterPagerAdapter(String[] posters) {
        this.posters = posters;
    }

    @Override
    public int getCount() {
        return posters.length;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view.equals(object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View parentView = LayoutInflater.from(container.getContext()).inflate(R.layout.poster_image_view, container, false);
        ImageView imageView = (ImageView) parentView.findViewById(R.id.poster);
        mImageLoader.loadImage(posters[position].trim(), imageView);
        container.addView(parentView, 0);
        return parentView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        View parentView = (View) object;
        container.removeView(parentView);
    }
}

This is probably caused by #288, we're just not downsampling these images as much as we could.

That said it also depends on the size of the views. You may want to consider manually decreasing the size of the views or using override to set the size Glide uses. Loading 5-6 30mb images isn't going to work well. If it is #288, then pulling forward should help.

Actually I'am confused that why downloading an 200kb size, 2000*3000 dimensions image would allocate 30mb memory.

In Android, as with most frameworks, Bitmaps are uncompressed. As a result, the size they take in memory is based on the number of pixels in the image and the format of the Bitmap.

Glide uses one of two formats, RGB_565 and ARGB_8888. Glide prefers RGB_565, but since it doesn't support transparency, falls back to ARGB_8888 for certain images (and/or via a setting).

RGB_565 is 2 bytes per pixel, so 2000 * 3000 * 2 = 12000000 or 12mb. ARGB_8888 is 4 bytes per pixel, so 2000 * 3000 * 4 = 24000000 or 24mb. Since your images are somewhat larger, with the ARGB_8888 format, 30mb is about what we'd expect.

Since images of 2k by 3k are typically much larger than the view they would be displayed in, downsampling can help reduce the memory usage by only including a subset (1/2, 1/4, 1/8 etc) of the pixels in the Bitmap. If we don't downsample appropriately, or we can't because the view is sufficiently large, you will end up with a very large Bitmap. Decreasing the view size or using override (or pulling forward past the first for #288) will make it easier for Glide to downsample the images and should reduce the memory used. You could also try fitCenter() or centerCrop() to scale the images down to exactly your view size.

Thank you very much for your detailed explanation, it's really of great help. After digging into the source, i understand it better.Now i switched to DecodeFomat RGB_565 and download smaller pics via server, my problem finally solved! Final question, is there a way to clear the memory after the activity is destroyed.Cause i see a method called "clearMemory" in Glide. The scenario is: I have a viewPagerActivity which loads a set of big images(2 or 3 pictures), and it eats a lot of memory when downloading and decoding.Since Glide has disk cache, I assume it's reasonable to clear bitmap pool and memory cache when the activity is destroyed.But Gilde.get(context).clearMemory seems not help, the memory did't reduce after i leave the activity while i was monitoring memory via android studio.

You can use the ComponentCallbacks interface, override those methods in your Application object and call clear or trim memory on Glide then. There is a callback for when the application is backgrounded I believe.

It's possible that you're not seeing any change with those methods currently because requests are cleared in onDestroy in a viewless Fragment, which may be called before or after your Activity's onDestroy.

glide

does newest Glide solove memory leak in Android 2.3?

Was this page helpful?
0 / 5 - 0 ratings