Android随笔-常见的OOM Android 中常见的 OOMOut Of Memory场景可以从Java 堆内存、Native 内存、Bitmap 内存、线程泄漏、大对象分配等几个维度来梳理。一、Java 堆内存 OOM1. 内存泄漏导致堆持续增长典型场景泄漏类型具体表现根因单例持有 Activity退出 Activity 后对象无法回收static单例引用Activity作为ContextHandler / Runnable 匿名内部类发送的延迟消息持有外部 Activity 引用匿名内部类隐式持有外部类引用消息未处理完 Activity 无法销毁监听器未注销广播、传感器、定位、网络回调持续持有registerReceiver/registerListener后未unregister资源未关闭Cursor、File、Stream、数据库连接泄漏未在finally中关闭或用完未置nullWebView退出页面后内存不降WebView 持有 Activity 引用且 Native 层有独立渲染线程修复方向单例用 ApplicationContext 替代 ActivityContextHandler 改为静态内部类 WeakReferenceActivity onDestroy 时 removeCallbacksAndMessages(null)所有注册成对注销用 LifecycleObserver 自动管理资源用 try-with-resources 或 use 块WebView 单独进程 onDestroy 中 destroy() 从父容器 removeView2. 集合类无限增长典型场景List、Map、Set 作为成员变量只 add 不 remove缓存无上限如图片内存缓存、网络请求缓存日志缓冲区、事件总线EventBus订阅者列表膨胀修复方向使用 LruCache 替代无界 HashMap设置合理 maxSize用 WeakHashMap 或 SoftReference 做二级缓存定期清理或设置 TTLTime To Live3. 字符串 / 大对象频繁创建典型场景循环内大量 String 拼接产生大量中间对象JSON 解析大文件时一次性加载到内存XML / HTML 解析用 DOM 方式整棵树驻留内存修复方向循环内用 StringBuilder 替代 拼接大文件用流式解析JsonReader、XmlPullParser避免在 onDraw、onTouchEvent 中创建对象二、Bitmap 内存 OOM这是 Android 中最常见的 OOM 类型之一。场景说明加载超大分辨率图片直接BitmapFactory.decodeFile()加载 4K 图片不采样图片列表快速滑动RecyclerView 中每个 Item 都加载原图无复用内存中缓存过多大图图片内存缓存无上限同时持有几十张高清图ARGB_8888 格式滥用每个像素 4 字节一张 1920×1080 图约 8MB修复方向// 1. 采样加载inSampleSizeBitmapFactory.OptionsoptionsnewBitmapFactory.Options();options.inJustDecodeBoundstrue;BitmapFactory.decodeResource(res,resId,options);options.inSampleSizecalculateInSampleSize(options,reqWidth,reqHeight);options.inJustDecodeBoundsfalse;BitmapbitmapBitmapFactory.decodeResource(res,resId,options);// 2. 复用 Bitmap 内存inBitmapAPI 11options.inBitmapreusableBitmap;// 3. 用 Glide / Coil 自动管理// 内部已封装采样、缓存、复用、生命周期绑定Glide.with(context).load(url).into(imageView);三、Native 内存 OOM典型场景场景说明Bitmap 像素数据API 10 前旧版本 Bitmap 像素存在 Native 堆Java 堆只持有一个int指针大量 JNI 调用未释放引用NewGlobalRef/NewLocalRef后未DeleteGlobalRefNative 库内存泄漏第三方 so 库如地图、音视频内部泄漏RenderScript / OpenGL 纹理纹理对象未释放GPU/Native 内存持续增长修复方向及时调用 bitmap.recycle()API 10 前必须JNI 层严格配对 NewGlobalRef / DeleteGlobalRef用 NativeMemoryProfiler 或 malloc_debug 追踪 Native 分配谨慎使用 android:largeHeap“true”它只是推迟 OOM不是解决方案四、线程泄漏导致 OOM典型场景场景说明AsyncTask 滥用每个网络请求都new AsyncTask线程池无限增长自定义 Thread 不停止后台轮询线程while(true)无退出条件线程池参数不当Executors.newCachedThreadPool()无界队列任务堆积Timer / ScheduledExecutor 未取消定时任务持续创建新线程修复方向// 用协程或有限线程池ExecutorServiceexecutorExecutors.newFixedThreadPool(4);// Kotlin 协程lifecycleScope.launch(Dispatchers.IO){// 自动绑定生命周期退出时取消}// WorkManager 替代常驻后台线程PeriodicWorkRequestworknewPeriodicWorkRequest.Builder(MyWorker.class,15,TimeUnit.MINUTES).build();WorkManager.getInstance(context).enqueue(work);五、大对象分配直接触发 OOM典型场景场景说明一次性加载超大数组byte[100 * 1024 * 1024]直接申请 100MB大文件转 Base6410MB 文件转 Base64 后字符串约 13MB再复制一次约 26MB深拷贝大对象序列化反序列化大对象图反射创建大对象Array.newInstance()传入超大 size修复方向大文件分片处理用流式读写避免不必要的对象拷贝用 ByteBuffer 直接操作反射创建数组前校验 size 合理性六、系统限制导致的 OOM场景说明App 堆内存上限低端机 16MB中端 64MB高端 256MB由dalvik.vm.heapsize决定进程内存上限32 位进程虚拟地址空间 3GB大量 so 映射 堆分配可能耗尽FD文件描述符耗尽超过 1024 个 FD 后open()失败间接导致内存分配异常七、OOM 排查工具链工具用途Android Studio Memory Profiler实时查看堆内存触发 GCDump HPROFLeakCanary开发期自动检测 Activity / Fragment 泄漏生成引用链MAT (Memory Analyzer Tool)分析 HPROF 文件查找 Dominator Tree、Path to GC RootsStrictMode检测 Activity 泄漏、未关闭资源、SQLite 未关闭adb shell dumpsys meminfo查看进程 PSS / USS / VSS 内存分布Native Memory ProfilerAndroid Studio 4.1 支持 Native 堆分析八、总结OOM 预防 checklist□ 单例用 ApplicationContext不用 Activity Context□ Handler 改为静态内部类 WeakReferenceonDestroy 移除消息□ 所有 register 成对 unregister用 LifecycleObserver 自动管理□ 资源 try-with-resources 关闭WebView 单独进程并 destroy□ Bitmap 用 inSampleSize 采样 inBitmap 复用或用 Glide/Coil□ 缓存用 LruCache设置合理上限和过期策略□ 线程用协程 / FixedThreadPool / WorkManager避免无限创建□ 大文件流式处理避免一次性加载到内存□ 定期 Memory Profiler 检查堆增长趋势线上监控 OOM 率OOM 的本质是内存分配请求超过了可用内存要么减少分配要么及时释放要么提升上限但提升上限是下策。