Android随笔-常见的OOM

Android 中常见的 OOM(Out Of Memory)场景,可以从Java 堆内存、Native 内存、Bitmap 内存、线程泄漏、大对象分配等几个维度来梳理。

一、Java 堆内存 OOM

1. 内存泄漏导致堆持续增长

典型场景:

泄漏类型 具体表现 根因
单例持有 Activity 退出 Activity 后对象无法回收 static 单例引用 Activity 作为 Context
Handler / Runnable 匿名内部类 发送的延迟消息持有外部 Activity 引用 匿名内部类隐式持有外部类引用,消息未处理完 Activity 无法销毁
监听器未注销 广播、传感器、定位、网络回调持续持有 registerReceiver / registerListener 后未 unregister
资源未关闭 Cursor、File、Stream、数据库连接泄漏 未在 finally 中关闭或用完未置 null
WebView 退出页面后内存不降 WebView 持有 Activity 引用,且 Native 层有独立渲染线程

修复方向:

  • 单例用 ApplicationContext 替代 ActivityContext
  • Handler 改为静态内部类 + WeakReference,Activity onDestroy 时 removeCallbacksAndMessages(null)
  • 所有注册成对注销,用 LifecycleObserver 自动管理
  • 资源用 try-with-resources 或 use 块
  • WebView 单独进程 + onDestroy 中 destroy() + 从父容器 removeView

2. 集合类无限增长

典型场景:

  • List、Map、Set 作为成员变量,只 add 不 remove
  • 缓存无上限,如图片内存缓存、网络请求缓存
  • 日志缓冲区、事件总线(EventBus)订阅者列表膨胀

修复方向:

  • 使用 LruCache 替代无界 HashMap,设置合理 maxSize
  • 用 WeakHashMap 或 SoftReference 做二级缓存
  • 定期清理或设置 TTL(Time To Live)

3. 字符串 / 大对象频繁创建

典型场景:

  • 循环内大量 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

修复方向

java 复制代码
// 1. 采样加载:inSampleSize
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);

// 2. 复用 Bitmap 内存:inBitmap(API 11+)
options.inBitmap = reusableBitmap;

// 3. 用 Glide / Coil 自动管理
// 内部已封装采样、缓存、复用、生命周期绑定
Glide.with(context).load(url).into(imageView);

三、Native 内存 OOM

典型场景

场景 说明
Bitmap 像素数据(API 10 前) 旧版本 Bitmap 像素存在 Native 堆,Java 堆只持有一个 int 指针
大量 JNI 调用未释放引用 NewGlobalRef / NewLocalRef 后未 DeleteGlobalRef
Native 库内存泄漏 第三方 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 未取消 定时任务持续创建新线程

修复方向

java 复制代码
// 用协程或有限线程池
ExecutorService executor = Executors.newFixedThreadPool(4);

// Kotlin 协程
lifecycleScope.launch(Dispatchers.IO) {
    // 自动绑定生命周期,退出时取消
}

// WorkManager 替代常驻后台线程
PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(
    MyWorker.class, 15, TimeUnit.MINUTES).build();
WorkManager.getInstance(context).enqueue(work);

五、大对象分配直接触发 OOM

典型场景

场景 说明
一次性加载超大数组 byte[100 * 1024 * 1024] 直接申请 100MB
大文件转 Base64 10MB 文件转 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 实时查看堆内存,触发 GC,Dump HPROF
LeakCanary 开发期自动检测 Activity / Fragment 泄漏,生成引用链
MAT (Memory Analyzer Tool) 分析 HPROF 文件,查找 Dominator Tree、Path to GC Roots
StrictMode 检测 Activity 泄漏、未关闭资源、SQLite 未关闭
adb shell dumpsys meminfo 查看进程 PSS / USS / VSS 内存分布
Native Memory Profiler Android Studio 4.1+ 支持 Native 堆分析

八、总结:OOM 预防 checklist

□ 单例用 ApplicationContext,不用 Activity Context

□ Handler 改为静态内部类 + WeakReference,onDestroy 移除消息

□ 所有 register 成对 unregister,用 LifecycleObserver 自动管理

□ 资源 try-with-resources 关闭,WebView 单独进程并 destroy

□ Bitmap 用 inSampleSize 采样 + inBitmap 复用,或用 Glide/Coil

□ 缓存用 LruCache,设置合理上限和过期策略

□ 线程用协程 / FixedThreadPool / WorkManager,避免无限创建

□ 大文件流式处理,避免一次性加载到内存

□ 定期 Memory Profiler 检查堆增长趋势,线上监控 OOM 率

OOM 的本质是内存分配请求超过了可用内存,要么减少分配,要么及时释放,要么提升上限(但提升上限是下策)。

相关推荐
-SOLO-8 小时前
Android Event 日志完全指南
android
壮哥_icon8 小时前
【Android 系统开发】从掉帧卡顿到丝滑:高频硬件中断(IRQ)的 CPU 亲和性性能优化实战
android·性能优化
千里马学框架9 小时前
Android Framework 新手学习踩坑建议指南
android·智能手机·性能优化·framework·aaos·车载开发·系统工程师
我命由我1234511 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
狂奔solar12 小时前
ARMA 模型:从白噪声到万能逼近器
android
渣渣灰飞12 小时前
MySQL 系统学习 第五阶段:企业级 MySQL 实战开发 第二章:RBAC 权限系统设计
android·学习·mysql
qq_4228286212 小时前
android 图形学之图层数据送显及合成图层(七)
android
MiyamuraMiyako13 小时前
Compose:从自动滚动到双锚点 LazyLayout
android·android jetpack
用户634214199350715 小时前
base_logger_core --- 跨平台 C++ 日志核心库
android
苦瓜花15 小时前
【Kotlin】初入门
android·开发语言·kotlin