跳到主要内容

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 控制缓存上限

相关链接​