【问题标题】:OutOfMemoryError although vm has enough free memoryOutOfMemoryError 虽然 vm 有足够的可用内存
【发布时间】:2013-12-26 15:34:47
【问题描述】:

尽管 dalvikvm 报告了足够的堆空间,但我得到了这个奇怪的 OutOfMemoryError。日志:

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)

正如您在内存不足发生之前看到的那样,dalvikvm 在 gc 之后报告了大约 10mb 的可用内存。分配用于 800k 位图。我怀疑这里的 gc 和位图解码之间存在竞争条件,因为在崩溃前最后 20-30 秒的所有日志语句中,报告的 dalvik 的可用内存并未低于 8mb 可用内存。

此问题出现在运行 Android 4.1.2 的三星 Galaxy Tab 2 10.1 上。 我正在使用 Google I/O 应用程序 (2012) 中的 ImageFetcher 类的修改版本,因此在加载图像以优化 sampleSize 选项时,我已经在执行 inJustDecodeBounds 之类的操作。

根据Managing Bitmap Memory 中的文档,Android 在 dalvik 堆中分配位图像素数据(自 Android 3.0 起),那么为什么解码位图会导致 10mb 空闲内存的内存不足

有没有人以前见过这种情况或可能知道发生了什么?

编辑: 每个请求 here 是来自 Google I/O 应用程序 2012 的图像加载代码。 在我的应用中,我只是在打电话

mImageFetcher.loadImage(myUrl, myImageView);

编辑2: 从上面的链接中提取的相关图像解码方法表明我已经在使用样本大小优化:

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;
}

【问题讨论】:

  • 您能分享代码以便我们为您提供帮助吗?
  • 你需要回收你的位图
  • @Manitoba 添加了源链接
  • @ShakeebAyaz recycle 仅释放本机内存,文档称这仅适用于 3.0 以下的 Android 版本。如果文档有误,请纠正我。
  • 您应该尝试提供有关您的整个应用程序的更多信息。系统不可能在平板电脑上仅占用 38 Mb 就杀死您的应用程序。你确定你没有使用一些本机内存吗?您的应用程序的限制对于 Native memory + java heap 是独一无二的。你在使用一些本地库吗?也许是opencv或gmaps?尝试使用 MAT 跟踪 java 总内存和本机内存使用情况wwboy6.wordpress.com/2012/12/09/android-ndk-memory-analyze

标签: android android-memory


【解决方案1】:

当您有 10 Mb 空闲空间时,关于大约 850000 字节分配的 OutOfMemory (OOM),这肯定是由于内存碎片造成的,无法保证堆有一个大于 850000 字节的连续内存块,这就是为什么您获取 OOM。

奇怪的是你仍然得到错误,你似乎已经做了一些优化,你真的释放你持有的所有内存吗?我的意思是你有 38 Mb 的已用堆,那块内存中包含什么?

您是否尝试过查看图像加载库,例如 picasso

在哪里可以: Picasso.with(context).load("@987654322@").fit().into(imageView);

(这会下载并缓存图像,并适合并绘制到 imageView 中,整洁!)

更新

  1. 您应该在 MAT 中分析您的 hprof 文件(应该在根 sdcard 路径上可用,因为您获得了 OOM),以查看您是否持有不必要的引用
  2. 释放这些引用(将它们清空以让 GC 收集它们)
  3. 使用inBitmap 重用内存(在 KitKat 中变得更强大,图像不需要与之前的图像大小相同,只需与之前的内存一样多或更少)
  4. 如果您经常要求相同的图像,请考虑使用例如缓存LruCache
  5. 如果它们是非常大的位图,请尝试平铺图像(将小方形位图加载到大图像中)查看:TileImageView(虽然这是使用 GlView 进行绘制..)

【讨论】:

  • 谢谢。内存碎片听起来像是对这种行为的合理解释。我看过 Picasso 并在另一个项目中使用它,但我们使用的基本上是 Volley 的图像加载部分,并提供类似的功能。我们可能会在下一个产品迭代中添加 inBitmap 加载,这应该是另一种内存优化,但如果它确实是一个碎片问题,应该特别有用。如果您有任何关于 Android 内存碎片的(半)官方链接,我很乐意将赏金奖励给您。再次感谢。
  • memory fragmentation 讨论了当我们分配大对象时会发生什么,问题是堆中的小对象污染了堆,最终阻止了大对象的分配。它也已在 SO 上的许多其他线程中讨论过。您可以用来防止 OOM 发生的一种策略是池化您的位图(保留大​​内存而不是一次又一次地重新分配相同的块)。
  • 在我的情况下,这可能是 Google Maps 的组合分配了未显示在堆转储中的本机堆负载,也可能是碎片问题。由于 Pasquale 只发布了评论而不是答案,我将奖励赏金给你。感谢您的意见。
【解决方案2】:

似乎 ICS 和更高版本的 Android 不会让您的 VM 达到堆的总大小。我在我的应用中看到了同样的东西,

你可以添加

android:largeHeap="true" 

到您的 ,这为您的应用程序提供了更大的堆。不好,但工作......

【讨论】:

  • 感谢您的回答。将 largeHeap 设置为 true 有什么缺点吗?该应用程序是否仍能在内存不足的设备上顺利安装和运行?您是否知道任何向下兼容到 2.2 的问题?我对此进行了一些研究,但没有发现任何结论。
  • 我在低至 API 8 级的模拟器和 Apkudo 上对其进行了测试,并且可以正常工作。不过,我没有推出这个版本。如果该属性在某个 API 级别是未知的,它将被简单地忽略。如果您使用的是共享用户 ID,则需要同时推出自启动流程规则的应用程序以来的所有应用程序。我注意到在 KitKat 上,我的应用程序无缘无故地遇到了 OutOfMemoryExceptions,并且该标志使这种情况消失,没有其他明显的影响。我在某处读到 KitKat 会关闭消​​耗过多内存的应用程序,因此 largeHeap 标志可能只是禁用了此“功能”。
  • 除此之外,我的直觉告诉我,有一天会受到惩罚:-S
  • 强烈建议不要使用大堆,除非您真的需要(例如,您必须在平板电脑或类似设备上管理大图像)。如果您遇到 OOM 错误,您应该真正尝试优化使用图像的方式,例如将它们缩小到您需要的大小或减少图像缓存(如果有的话)
  • 那真的不是解决问题,只是隐藏它(再长一点)
【解决方案3】:

试试这个

public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){
        try {
            //Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,o);

            //The new size we want to scale to
            final int REQUIRED_WIDTH=WIDTH;
            final int REQUIRED_HIGHT=HIGHT;
            //Find the correct scale value. It should be the power of 2.
            int scale=1;
            while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
                scale*=2;

            //Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        }
            catch (FileNotFoundException e) {}
        return null;
    }

这将根据您传递的宽度和高度缩放位图。此函数根据图像分辨率找到正确的比例。

【讨论】:

    【解决方案4】:

    此代码将帮助您完成,试试这个

    public Bitmap decodeSampledBitmapFromResource(String path,
            int reqWidth, int reqHeight) {
    
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        // BitmapFactory.decodeResource(getResources(), id)
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);
    
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(path, options);
    }
    
    public 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;
        }
    
        return inSampleSize;
    }
    

    【讨论】:

    【解决方案5】:

    我是新手,我不确定,但尝试像这样对图像进行采样

      final BitmapFactory.Options options = new BitmapFactory.Options();
      options.inSampleSize = 8;
    
      Bitmap bm=BitmapFactory.decodeFile(strPath,options);
    

    使用 inSampleSize 将比例位图加载到内存中。对 inSampleSize 值使用 2 的幂对于解码器来说更快、更有效。但是,如果您计划将调整大小的版本缓存在内存或磁盘上,通常仍值得将其解码为最合适的图像尺寸以节省空间。

    【讨论】:

    • 谢谢。我已经在进行样本大小优化。编辑了我的问题以使其更清楚。
    【解决方案6】:

    创建 Imageloader 类

    package com.example.model;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Collections;
    import java.util.Map;
    import java.util.WeakHashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import android.app.Activity;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Matrix;
    import android.widget.ImageView;
    
    public class ImageLoader {
    
        MemoryCache memoryCache=new MemoryCache();
        FileCache fileCache;
        private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
        ExecutorService executorService; 
    
        public ImageLoader(Context context){
            fileCache=new FileCache(context);
            executorService=Executors.newFixedThreadPool(5);
        }
    
        final int stub_id=R.drawable.load;
        public void DisplayImage(String url, ImageView imageView)
        {
            imageViews.put(imageView, url);
            Bitmap bitmap=memoryCache.get(url);
            if(bitmap!=null)
                imageView.setImageBitmap(bitmap);
            else
            {
                queuePhoto(url, imageView);
                imageView.setImageResource(stub_id);
            }
        }
    
        private void queuePhoto(String url, ImageView imageView)
        {
            PhotoToLoad p=new PhotoToLoad(url, imageView);
            executorService.submit(new PhotosLoader(p));
        }
    
        private Bitmap getBitmap(String url) 
        {
            File f=fileCache.getFile(url);
    
            //from SD cache
            Bitmap b = decodeFile(f);
            if(b!=null)
                return b;
    
            //from web
            try {
                Bitmap bitmap=null;
                URL imageUrl = new URL(url);
               // System.out.println("url :"+imageUrl);
                HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
                conn.setConnectTimeout(30000);
                conn.setReadTimeout(30000);
                conn.setInstanceFollowRedirects(true);
    
                InputStream is=conn.getInputStream();
                OutputStream os = new FileOutputStream(f);
                Utils.CopyStream(is, os);
    
                os.close();
                bitmap = decodeFile(f);
    
    
                return bitmap;
            } catch (Throwable ex){
               ex.printStackTrace();
               if(ex instanceof OutOfMemoryError)
                   memoryCache.clear();
               return null;
            }
        }
    
        //decodes image and scales it to reduce memory consumption
        private Bitmap decodeFile(File f){
            try {
                //decode image size
                BitmapFactory.Options o = new BitmapFactory.Options();
                o.inJustDecodeBounds = true;
                o.inPurgeable = true; // Tell to garbage collector that whether it needs free memory, the Bitmap can be cleared
                o.inTempStorage = new byte[32 * 1024];
                BitmapFactory.decodeStream(new FileInputStream(f),null,o);
    
                //Find the correct scale value. It should be the power of 2.
                final int REQUIRED_SIZE=70;
                int width_tmp=o.outWidth, height_tmp=o.outHeight;
                int scale=1;
                while(true){
                    if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                        break;
                    width_tmp/=2;
                    height_tmp/=2;
                    long heapSize = Runtime.getRuntime().maxMemory();
    
                   long heapsize1=(heapSize/(1024*1024));
                   if(heapsize1>95)
                   {
                      scale*=1;
                     // System.out.println("scale1 :");
                   }else if(heapsize1>63 && heapsize1<=95){
                      scale*=2;
                    // System.out.println("scale2 :");
                   }else if(heapsize1>31 && heapsize1<=63){
                       scale*=2;
                     // System.out.println("scale22 :");
                   }else if(heapsize1>0 && heapsize1<=31){
                          scale*=2;
                        // System.out.println("scale23 :");
                       }
                   /*else if(heapsize1>31 && heapsize1<=63){
                      scale*=2;
                    // System.out.println("scale2 :");
                   }else if(heapsize1>0 && heapsize1<=31){
                      scale*=2;
                        // System.out.println("scale2 :");
                       }*/
    
                }
    
                //decode with inSampleSize
                BitmapFactory.Options o2 = new BitmapFactory.Options();
                o2.inPurgeable = true; // Tell to garbage collector that whether it needs free memory, the Bitmap can be cleared
                o2.inTempStorage = new byte[32 * 1024];
                o2.inSampleSize=scale;
                Bitmap bitmap1=BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
              //  System.out.println("width : "+bitmap1.getWidth()+ " height : "+bitmap1.getHeight());
           /*     if(bitmap1.getHeight()>=bitmap1.getWidth())
                {
    
                    bitmap1 = Bitmap.createScaledBitmap(bitmap1, bitmap1.getHeight(),bitmap1.getWidth(), true);
                }else{
                    //bmp = Bitmap.createScaledBitmap(bmp, (int) height2,width, true);
                    Matrix matrix = new Matrix();
    
                    matrix.postRotate(270);
                    bitmap1 = Bitmap.createBitmap(bitmap1 , 0, 0, bitmap1 .getWidth(), bitmap1 .getHeight(), matrix, true);
    
                }*/
                return bitmap1;
            } catch (FileNotFoundException e) {}
            return null;
        }
    
        //Task for the queue
        private class PhotoToLoad
        {
            public String url;
            public ImageView imageView;
            public PhotoToLoad(String u, ImageView i){
                url=u; 
                imageView=i;
            }
        }
    
        class PhotosLoader implements Runnable {
            PhotoToLoad photoToLoad;
            PhotosLoader(PhotoToLoad photoToLoad){
                this.photoToLoad=photoToLoad;
            }
    
            @Override
            public void run() {
                if(imageViewReused(photoToLoad))
                    return;
                Bitmap bmp=getBitmap(photoToLoad.url);
                memoryCache.put(photoToLoad.url, bmp);
                if(imageViewReused(photoToLoad))
                    return;
                BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
                Activity a=(Activity)photoToLoad.imageView.getContext();
                a.runOnUiThread(bd);
            }
        }
    
        boolean imageViewReused(PhotoToLoad photoToLoad){
            String tag=imageViews.get(photoToLoad.imageView);
            if(tag==null || !tag.equals(photoToLoad.url))
                return true;
            return false;
        }
    
        //Used to display bitmap in the UI thread
        class BitmapDisplayer implements Runnable
        {
            Bitmap bitmap;
            PhotoToLoad photoToLoad;
            public BitmapDisplayer(Bitmap b, PhotoToLoad p){
                bitmap=b;photoToLoad=p;
                }
            public void run()
            {
                if(imageViewReused(photoToLoad))
                    return;
                if(bitmap!=null)
                    photoToLoad.imageView.setImageBitmap(bitmap);
                else
                    photoToLoad.imageView.setImageResource(stub_id);
            }
        }
    
        public void clearCache() {
            memoryCache.clear();
            fileCache.clear();
        }
    
    }
    

    使用它

    或使用该链接lazy loader example

    【讨论】:

      【解决方案7】:

      首先,所有答案都将帮助您降低内存消耗,但我们没有人可以真正帮助您的应用程序,因为我们不知道整个代码。

      我将与您分享我使用我们的应用程序的经验。我们遇到了太多 OOM,我们尝试了 Picasso、Android 中的 BitmapFun 示例,最后我决定分析我的应用程序。

      Android Studio 为您提供了一个名为 Monitor 的工具,您可以在其中跟踪您的应用在您调用的每个 Activity、每次旋转等过程中在 RAM 中分配了多少内存。

      您可以使用 HEAP Dump 导出刚刚完成的分析,然后将其导入 Eclipse Memory Analyzer Tool。在那里你可以运行一个对象泄漏检测器(或类似的东西。它会帮助你显示哪些对象在 RAM 中泄漏。

      I.E.我们使用的是旧方法(OnRetainCustomConfig ...),您可以在其中存储对象的引用。问题是新 Activity 收到了旧 Activity 对象的引用,因此,GC 没有成功清理旧 Activity,因为它假定它们仍在使用中。

      希望我的评论对你有帮助

      【讨论】:

      • 你当然是对的。而且我并不是专门寻找解决问题的方法,而是我想了解为什么即使 VM 报告有 10MB 可用内存,也会抛出这种奇怪的内存不足。就我而言,我认为这可能是 Google Maps 分配未显示在堆转储中的本机内存以及可能还有内存碎片的组合,因此该线程中的讨论已经非常有帮助。谢谢你的回答。
      • 试一试 Eclipse MAT,在那里你可以得到一份 Leaks Suspects Report,它比试图猜测要好
      猜你喜欢
      • 1970-01-01
      • 2011-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-09
      • 1970-01-01
      • 2023-03-31
      相关资源
      最近更新 更多