Android应用中OOM问题剖析和解决方案

深度学习模型部署 深度学习移动端部署 https://blog.youkuaiyun.com/karry_zzj/article/details/102827337?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2. 阅读详情

1.什么是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
这几句的意思是,我们程序申请需要10485776byte太大了,虚拟机无法满足我们,羞愧的shutdown自杀了。这个现象通常出现在用到很多图片或者很大图片的APP开发中。通俗讲就是当我们的APP需要申请一块内存来装图片的时候,系统觉得我们的APP所使用的内存已经够多了。即使它有1G的空余内存,它不同意给我的APP更多的内存里,然后即使系统马上抛出OOM错误,而程序没有捕捉该错误,故弹框崩溃了。


2.为什么会有OOM?

因为android系统的app的每个进程或者每个虚拟机有个最大内存限制,如果申请的内存资源超过这个限制,系统就会抛出OOM错误。跟整个设备的剩余内存没太大关系。比如比较早的android系统的一个虚拟机最多16M内存,当一个app启动后,虚拟机不停的申请内存资源来装载图片,当超过内存上限时就出现OOM。Android系统的APP内存限制怎么确定?

2.1 Android的APP内存组成:

        APP内存由dalvik内存和native内存2部分组成,dalvik也就是java堆,创建的对象就是就是在这里分配的,而native是通过c/c++方式申请的内存,Bitmap就是以这种方式分配的。(android3.0以后,系统都默认通过dalvik分配的,native作为堆来管理)。这2部分加起来不能超过android对单个进程,虚拟机的内存限制。
每个手机的内存限制大小是多少?
        ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
        activityManager.getMemoryClass();

       以上方法会返回以M为单位的数字,不同的系统平台或设备上的值都不太一样,比如HTC默认24M, Galaxy36M, emulator-2.3 24M,等等。我的moto xt681是42M。3
上面取到是虚拟机的最大内存资源。
       而对于head堆的大小限制,可以查看/system/build.prop文件。
       dalvik.vm.heapstartsize = 5m
       dalvik.vm.heapgrowthlimit=48m
       dalvik.vm.heapsize=256m
 注: heapsize参数表示单个进程heap可用的最大内存,但如果存在以下参数"dalvik.vm.headgrowthlimit =48m"表示单个进程heap内存被限定在48m,即程序运行过程实际只能使用48m内存。

2.2 为什么android系统设定APP的内存限制?

        1  要使开发者内存使用更为合理。限制每个应用的可用内存上限,可以放置某些应用程序恶意或者无意的使用过多的内存。而导致其它应用无法正常运行。Android是有多进程的,如果一个进程(就是一个应用)耗费过多的内存,其他应用就无法运行了。因为有了限制,使得开发者必须好好利用有限资源,优化资源的使用。

        2  屏幕显示内容有限,内存足够即可。即使有万千图片千万数据需要使用到,但在特定时刻需要展示给用户看的总是有限的,因为屏幕显示就那么大,上面可以放的信息就是很有限的。大部分信息都是处于准备显示状态,所以没必要给予太多heap内存。也就是说出现OOM现象,绝大部分原因是我们的程序设计上有问题,需要优化。优化方法很多,比如通过时间换空间,不停的加载要用的的图片,不停的回收不用的图片,把大图片解析成适合手机屏幕大小的图片等。

        3  多APP多个虚拟机davlik的限制需要。android上的app使用独立虚拟机,每开一个应用就会打开至少一个独立的虚拟机。这样可以避免虚拟机崩溃导致整个系统崩溃,同时代价就是需要浪费更多的内存。这样设计保证了android的稳定性。

2.3 不是GC自动回收资源么,为什么还会OOM?

       Android不是用GC会自动回收资源么,为什么app的那些不用的资源不回收呢?
       Android的gc会按照特定的算法回收程序不用的内存资源,避免app的内存申请约积越多,但是gc一般回收的资源是那些无主的对象内存或者软饮用的资源,或者更软引用的引用资源。比如:
       
       Bitmap bt = BitmapFactory.decodeResource(this.getResources(), R.drawable.splash); //此时的图片资源是强引用,是有主的资源。
       bt = null; //此时这个图片资源就是无主的了。gc心情号的时候就会去回收它。
       SoftReference<Bitmap> softRef = new SoftReference<Bitmap>(bt);
       bt = null;
       其他代码...

      当程序申请很多内存资源时,gc有可能会释放softref引用的这个图片内存。bt=softRef.get(),此时可能得到的是null,需要重新加载图片。
      当然这也说明了用软引用图片资源的好处,就是gc会自动根据需要释放资源,一定程度上避免OOM。

TIPS:编程要养成习惯,不用的对象设置为null。其实更好的是,不用的图片直接recycle。因为通过设置null让gc来回收,有时候还是会来不及。

2.4 怎么查看APP内存分配情况?

       1  通过DDMS中的heap选项卡监视内存情况:
        Heap视图中部有一个叫做data object, 即数据对象,也就是我们的程序中大量存在的类类型的对象。
        在data object一行中有一列是“Total Size”, 其值就是当前进程中所有Java数据对象的内存总量。如果代码中存在没有释放对象引用的情况,则data object的“Total Size”值在每次gc后不会有美线的回落。随着操作次数的增加“Total Size”的值会越来越大。直到到达一个上限 后导致进程被kill掉。
       2  在App里面我们可以通过totalMemory与freeMemory:
      Runtime.getRuntime().freeMemory()
      RUntime.getRuntime().totalMemory()
       3  adb shell dumpsys meminfo com.android.demo


3. 常见避免OOM的几个注意点:

     3.1 适当调整图像大小。因为手机屏幕尺寸有限,分配给图像的显示区域有限,尤其对于超大图片,加载自网络或者sd卡,图片文件提及达到几M或者十几个M的:
加载到内存前,先算出该bitmap的大小,然后通过适当调节采样率使得加载的图片刚好,或稍大捷克在手机屏幕上显示就满意了:
        
 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 图像缓存。在listview或Gallery等控件中一次性加载大量图片时,只加载屏幕显示的资源,尚未显示的不加载,移出屏幕的资源及时释放,采用强引用+软引用2级缓存,提高加载性能。缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存。
     3.3 采用低内存占用量的编码方式。比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存。
     3.4 及时回收图像。如果引用了大量的Bitmap对象,而应用又不需要同时显示所有图片。可以将暂时不用到的Bitmap对象及时回收掉。对于一些明确直到图片使用情况的场景可以主动recycle回收
     App的启动splash画面上的图片资源,使用完就recycle。对于帧动画,可以加载一张,画一张,释放一张。
     3.5 不要在循环中创建过多的本地变量。慎用static,用static来修饰成员变量时,该变量就属于该类,而不是该类实例,它的生命周期是很长的。如果用它来引用一些内存占用太多的实例,这时候就要谨慎对待了。
     3.6 自定义堆内存分配大小。优化Dalvik虚拟机的堆内存分配。
  
 public class ClassName{
                private static Context mContext;
                   // 省略
            }



4. App使用图片时避免OOM的几种方式:

     4.1 直接null或recycle
      对于app里使用的大量图片,采用方式:使用时加载,不显示时直接置null或recycle。
      这样处理是个好习惯,记本可以杜绝OOM,但是缺憾是代码多了,可能会忘记某些资源recycle。
      而有些情况下会出现特定图片反复加载,释放,再加载等,低效率的事情。
     4.2 简单通过SoftReference引用方式管理图片资源
      建个SoftReference的hashmap
      使用图片时先查询这个hashmap是否有softreference, softreference里的图片是否为空,
      如果为空就加载图片到softreference并加入hashmap。
      无需再代码里显式的处理图片的回收与释放,gc会自动处理资源的释放。
      这种方式处理起来简单实用,能一定程度上避免前一种方法反复加载释放的低效率。但还不够优化。
     4.3 强引用+软引用二级缓存
      Android示范程序ImageDownloader.java, 使用了一个二级缓存机制。就是有一个数据结构直接持有解码成功的Bitmap对象引用,同时使用一个二级缓存数据结构保持淘汰的Bitmap的softreference对象,由于softreference对象的特殊性,系统会再需要内存的时候首先将softreference持有的对象释放掉,也就是说当vm发现可用的内存较少需要出发gc的时候,二级缓存中的bitmap对象将被回收,而持有一级缓存的bitmap对象用于显示。
       
      其实这个解决方案最为关键的一点是使用了一个比较合适的数据结构,那就是LinkedHashMap类型来进行一级缓存Bitmap的容器。由于LinkeHashMap的特殊性,我们可以控制其内存存储对象的个数并且将不在使用的对象从容器中移除,放到softreference二级缓存里,我们可以在一级缓存中一致保存最近被访问到的bitmap对象,而已经被访问过的图片在LinkedHashMap的容量超过我们预设值时将会把容器中存在的时间最长的对象移除,这个时候我么可以将被移除的LinkedHashMap中的放到二级缓存容器,而二级缓存中的对象管理就交给系统来做了,当系统需要gc时就会首先回收二级缓存容器的Bitmap对象了。    
       
       在获取图片对象时候先从一级缓存容器中查找,如果有对应对象并可用直接返回,如果没有的话从二级缓存中查找对应的SoftReference, 判断SoftReference对象持有的Bitmap是否可用,可用直接返回,否则返回空。如果二级缓存都找不到图片,那就直接加载图片资源。
     4, LruCache  + sd的缓存方式


5. 两种容易OOM的场景建议:

   5.1 网络下载大量图片
    比如微博客户端: 多线程异步网络,小兔直接用LRUCache+SoftRef+Sd,大图按需下载:
   
    5.2 对于需要加载非常多条目信息的listview,gridview等的情况
    在adapter的getView函数里有个convertView参数,告知你是否有可重用的view对象。 如果不使用convertView的话,每次调用getView时每次都会重新创建view,这样之前的view可能还没销毁,加之不断的新建view势必会造成内存剧增,从而导致OOM。另外在重用convertView时,里面原有的图片等资源就会变成无主的了。
   这里Google官方推荐使用:“convertview+静态类viewholder”
   官方给出解释是:
    a 重用缓存convertView传递给getView()方法来避免填充不必要的视图。
    b 使用ViewHolder模式来避免没有必要的调用findViewById;因为太多的findViewById也会影响性能。

附ViewHolder类的作用:ViewHolder模式通过在getView方法返回的视图的标签(tag)中存储一个数据结构。这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById();

6 申请超过内存限制的内存分配方式:

   6.1 从Native C分配内存。使用NDK(本地开发工具包)和JNI, 它可能从C级(如malloc/free或新建/删除)分配内存,这样的分配是不计入24MB的限制。这是真的,从本机代码分配内存是为了java方便,但它可以被用来存储在ram的数据(即使图片数据)的一些打击呢。
   6.2 使用OpenGL的纹理。纹理内存不计入限制,要查看你的应用程序确实分配了多少内存可以使用android.os.Debug.getNativeHeapAllocatedSize(), 可以使用上面介绍的两种技术的Nexus之一,我可以轻松地为一个单一的前台进程分配300MB-10倍以上的默认24MB 的限制,从上面看来使用native代码分配内存是不在24MB的限制内的(开放的GL的质地也是使用native代码分配内存)。
   但是,这两个方法的风险就是,本地堆分配内存超过系统可用内存限制的话,通常都是直接崩溃。
     

        
Android性能优化之OOM篇 内存溢出是指程序在运行过程中占用的内存超过了系统分配的限制,导致无法为新对象分配内存,从而引发异常或崩溃。在Android开发中,系统会为每个应用分配一定的内存配额,当应用超出此配额时就会触发OOM错误。原因:直接加载过大的图片会占用大量内存。解决方案:通过进行图片压缩,或者使用优化的图片加载库如Glide、Picasso。合理规划缓存大小:根据设备内存情况,动态设置缓存的大小(如总内存的1/8)。使用LruCache管理缓存:确保缓存大小不会超过预设内存限制,并使用LRU策略移除不常用的缓存条目。 阅读详情

相关推荐

Android Camera内存问题剖析

本文通过一类 Android 机型上相机拍摄过程中的 native 内存 OOM 的问题展开,借助内存快照裁剪回捞和 Native 内存监控工具的赋能,来深入剖析此类问题。背景Rapha...

字节跳动技术团队官方博客 1万+

Android内存优化解决 资料和总结的经验分享

在前公司做一个图片处理的应用时, 项目交付的时候,客户的手机在运行应用的时候,一直在崩溃,而这个异常就是OutOfMemory的错误,简称为OOM, 搞得我们也是极其的崩溃,最后 ,我们是通过网上搜集资料和代码走查的方式来优化解决的,这里,我就把我们收集到资料和总结的经验分享下吧。     Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。我们平常看...

weixin_33812433的博客 514

Android oom_adj 内存水位算法剖析

从上文Android lmkd 机制详解中了解到lmkd 兼容了新的PSI 策略,以及旧是的kernel 驱动程序和oom adj 策略。Android 进程在不同的时候处于不同的进程状态,也会根据重要性动态调整进程的oom score。这样在lmkd 中可以根据当前的内存使用情况,找到合适的oom_score_adj,并将其kill 以满足内存的持续使用。本文主要剖析oom adj 算法,可以接Android lmkd 机制详解中第 8 节。

私房菜 5428

Android内存泄露OOM的原因及解决方法

什么是内存溢出? OOM(out of memory)即内存溢出.在程序中,对内存使用超过一定的阀值就会导致内存溢出,而new出来的Object对象在使用完后触发GC也无法被回收 叫做内存泄漏 OOM的可能导致的现象? 1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC) 2.由于APP运行内存限制,会导致直接崩溃(OutOfMemoryError) 3...

你只是看起很努力 1万+

安卓内存OOM问题深度分析与实战解决方案

Android内存管理建立在Linux内核基础之上,采用多层次架构设计。系统从下至上分为Linux内核层、运行时层(ART)与应用层,各层协同完成内存分配、回收与优化。内核通过页框分配器管理物理内存,而ART虚拟机则负责Java对象的堆内存分配与垃圾回收。应用进程由Zygote孵化,继承预加载类与资源,减少启动开销,但共享内存也增加了泄漏传播风险。该分层模型兼顾性能与隔离性,是理解OOM与内存泄露的前提。

weixin_36282234的博客 1284

解决Android平台中应用程序OOM异常的方法

在Android平台上面,应用程序OOM异常永远都是值得关注的问题。通常这一块也是程序这中的重点之一。这下我就如何解决OOM作一点简单的介绍。 首先,OOM就是内存溢出,即Out Of Memory。也就是说内存占有量超过了VM所分配的最大。 怎么解决OOM,通常OOM都发生在需要用到大量内存的情况下(创建或解析Bitmap,分配特大的数组等),在这样的一种情况下,就可能出现OOM,据我现在了解到,多数OOM都是因为Bitmap太大。所以,这里我就专门针对如何解决Bitmap的OOM。其实最核发的就是只加载可见范围内的Bitmap,试想这样一种情况,在GridView或ListView中,数据

cygwin 编译出现 fatal error - add_item (“\??“, “/“, ...) failed, errno 22

用 cygwin 编译一个程序编译完成后,在另外的电脑上运行出现如下错误: 编译机os:windows7 运行机os:windows7 D:\>x64-size_.exe 0 [main] x64-size_ (309288) D:\x64-size_.exe: *** fatal error - add_item ("\??", "/", ...) failed, errno 22 Stack trace: Frame Function Args 000F.

jiuweiljp的博客 2277

Windows下RISC-V开发环境搭建:xpack-riscv-none-elf-gcc安装配置全攻略

本文详细介绍了在Windows平台下搭建RISC-V开发环境的完整流程,重点讲解xpack-riscv-none-elf-gcc工具链的安装配置方法。从环境变量设置到VSCode集成开发环境搭建,涵盖汇编程序和C程序开发实践,为开发者提供一站式解决方案,助力快速掌握RISC-V架构开发技能。

weixin_26765287的博客 437

【Android】AnimationDrawable帧动画的进阶应用与性能优化

本文深入探讨了Android中AnimationDrawable帧动画的进阶应用与性能优化。文章不仅解析了其核心机制与生命周期,还分享了构建复杂动画序列、实现动态帧管理与条件跳转的实战技巧。重点剖析了在列表等场景下如何避免内存泄漏、控制帧率以提升流畅度,并提供了高级优化方案与替代方案,帮助开发者打造高性能的动画体验。

sunny的博客 176

Uniapp+Vue3+Monorepo:如何优化你的前端开发流程(附完整配置代码)

本文详细介绍了如何利用Uniapp+Vue3+Monorepo技术栈优化前端开发流程,提升代码可维护性和团队协作效率。通过Monorepo架构实现依赖管理智能化、代码复用最大化和构建流程统一化,结合完整配置代码和实战指南,帮助开发者快速构建高性能跨端应用。

weixin_29011993的博客 453

ERA5气象数据下载与解压全攻略:Python脚本一键搞定(附错误处理技巧)

本文提供了一套完整的ERA5气象数据自动化处理方案,涵盖从CDS API配置、智能批量下载到高效解压与数据验证的全流程。重点介绍了如何通过Python脚本实现一键式数据下载与批量解压,并分享了实用的错误处理技巧与性能优化策略,帮助科研人员高效获取和处理大规模气象数据。

k8l9m0n的博客 338

android 面试知识点总结(有一些自己写的,一些网上查资料找的)

1、如何对Android应用进行性能分析 答案: App启动优化 在APP冷启动、热启动、温启动过程中不要进行耗时的操作。 布局优化 尽量减少布局层级和复杂度 响应优化 主线程阻塞的:开辟单独的子线程来处理耗时阻塞事务. CPU满负荷, I/O阻塞的:I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行. 内存不够用的:增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等. 内存...

qq_21211945的博客 259

Spring AI 项目实战(五):Spring Boot + AI + DeepSeek + Redis 实现聊天应用上下文记忆功能(附完整源码)

本文介绍了如何基于Spring Boot 3.2、Spring AI 1.0.0-M4、DeepSeek API和Redis构建具备上下文记忆功能的智能聊天应用。项目采用现代化技术栈,包括Java 17、Maven、Lombok等,实现了智能对话、会话管理、异常处理等核心功能。文章详细讲解了环境搭建、配置管理(含Redis和AI服务集成)、核心组件设计(如消息实体、控制器和服务层实现),以及完整的项目结构。该项目提供了美观的Web界面和REST API,支持对话上下文存储(通过Redis实现)和系统监控,可

博客 829

深度起底内存泄漏连环炮:从 JVM 底层原理到 Android 实战避坑指南

本文深入解析了内存泄漏与内存溢出的核心区别,从底层内存分布到Android开发中的六大典型泄漏场景。内存溢出是申请空间不足,而泄漏是对象无法回收导致内存浪费。文章详细剖析了单例模式、静态变量、非静态内部类等常见泄漏场景,并提供了解决方案。同时介绍了内存抖动和垃圾回收机制的演进,帮助开发者全面理解内存管理原理。掌握这些知识不仅能解决实际开发问题,也能在面试中展示技术深度。

xhxh510573522125的博客 1260

IgH EtherCAT 从入门到精通:第 30 章 实战:高可用 EtherCAT 系统设计

摘要:构建高可用EtherCAT系统的关键技术 本章详细介绍了工业级EtherCAT系统的高可用性设计方法,主要包括: 冗余链路架构:采用双网卡环形拓扑设计,主备网卡同时工作,通过IgH Master的冗余配置实现线缆故障自动切换。 状态监控机制:提供专用API实时监控主备链路状态,包括从站响应数、链路状态和冗余激活标志,形成完整的故障判断矩阵。 自动恢复功能:系统内置FSM架构支持热插拔自动检测和重配置,从站断线/重连时可自动完成状态恢复,同时允许应用层实施安全措施。 这些技术共同构成了生产环境下Ethe

从事嵌入式linux android驱动开发,拥有全栈能力 243

Simulink开发项1000例实战专栏--实例38:基于Simulink的周波变换器(Cycloconverter)

需求分析周波变换器(Cycloconverter),也称为频率变换器或直接变频器,是一种将输入的固定频率交流电直接转换为不同频率的交流电的电力电子设备。它广泛应用于工业领域,如大功率电机驱动、风力发电、船舶推进系统等场合。与传统的间接变频器(如交-直-交变频器)相比,周波变换器具有更高的效率和更简单的结构,适用于需要低频输出的应用场景。

xiaoheshang_123的博客 334

快手客户端稳定性体系建设

背景 退出率 从Wakeups说起 Wakeups是怎么触发的 怎么监控wakeups 抓取堆栈 退出率定义 Android OOM治理 解决GC卡顿 解决Dump hprof冻结app 解决hprof文件过大 解决hprof解析的耗时与OOM 关键对象判定 性能优化 分发与跟进 收获与展望 成果 收获 Roadmap 参考文献 作者简介 招聘信息 开源项目KOOM 背景 当我们谈论稳定性的时候.

idaretobe的专栏 1497

【AI工具】Browser use搭建及使用的保姆级教程:让AI帮你自动操作浏览器

【AI工具】Browser use搭建及使用的保姆级教程:让AI帮你自动操作浏览器

funfan的博客 2535
上一篇: SurfaceView实现跑马灯
下一篇: Android圆角图片最佳方案
Zenip
博客等级 码龄14年 10粉丝 21原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值