跳到主要内容

OOM 排查

问题

Android 应用的 OOM 有哪些类型?如何排查?

答案

OOM 类型

类型触发条件常见原因
Java Heap OOMJava 堆超过限制大量 Bitmap、集合泄漏
Native Heap OOMNative 内存耗尽JNI 泄漏、大量 Native Bitmap
FD 耗尽文件描述符超过限制(1024)未关闭的流、数据库连接
线程 OOM线程数超过限制创建过多线程(无限线程池)
虚拟内存 OOM32位进程地址空间耗尽大量 mmap

Java Heap OOM

// ❌ 常见原因:大量 Bitmap 未释放
val bitmaps = mutableListOf<Bitmap>()
for (i in 1..100) {
bitmaps.add(BitmapFactory.decodeResource(resources, R.drawable.large_image))
// OOM: OutOfMemoryError: Failed to allocate a xxx byte allocation
}

// ✅ 修复:使用图片加载库 + 合适尺寸
Coil.imageLoader(context).enqueue(
ImageRequest.Builder(context)
.data(imageUrl)
.size(200, 200) // 按需加载
.target(imageView)
.build()
)

FD 泄漏 OOM

// ❌ 未关闭的流
fun readFile(path: String): String {
val reader = FileReader(path) // 打开 FD
return reader.readText() // 未关闭!
}

// ✅ use 自动关闭
fun readFile(path: String): String {
return FileReader(path).use { it.readText() }
}
# 查看进程的 FD 数量
adb shell ls -la /proc/<pid>/fd | wc -l
# 超过 1024 会 OOM

线程 OOM

// ❌ 无限创建线程
fun handleRequest() {
Thread { doWork() }.start() // 每次请求创建新线程
}

// ✅ 使用线程池
val executor = Executors.newFixedThreadPool(4)
fun handleRequest() {
executor.submit { doWork() }
}

// ✅✅ 更好:使用协程
suspend fun handleRequest() = withContext(Dispatchers.IO) {
doWork()
}

线上 OOM 监控

// 在 Application 中设置未捕获异常处理
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
if (throwable is OutOfMemoryError) {
// 上报 OOM 信息
reportOOM(throwable, getMemoryInfo())
}
}

fun getMemoryInfo(): String {
val runtime = Runtime.getRuntime()
return "maxMemory=${runtime.maxMemory() / 1024 / 1024}MB, " +
"totalMemory=${runtime.totalMemory() / 1024 / 1024}MB, " +
"freeMemory=${runtime.freeMemory() / 1024 / 1024}MB"
}

常见面试问题

Q1: OOM 一定是内存泄漏吗?

答案

不一定。OOM 可能是:

  1. 内存泄漏:GC Root 持有不该存活的对象
  2. 内存溢出:单次分配超大对象(如超大 Bitmap)
  3. 资源泄漏:FD 耗尽、线程耗尽
  4. 内存抖动:频繁分配短期对象导致碎片化

Q2: 如何预防 OOM?

答案

  1. 使用图片加载库(Coil/Glide),避免手动加载大 Bitmap
  2. 使用 try-with-resources / use 确保释放资源
  3. 使用线程池/协程而非裸线程
  4. 集合使用完及时 clear()
  5. 使用 onTrimMemory() 响应系统内存警告
  6. 大数据量使用 LruCache 控制缓存上限

相关链接