OOM 排查
问题
Android 应用的 OOM 有哪些类型?如何排查?
答案
OOM 类型
| 类型 | 触发条件 | 常见原因 |
|---|---|---|
| Java Heap OOM | Java 堆超过限制 | 大量 Bitmap、集合泄漏 |
| Native Heap OOM | Native 内存耗尽 | JNI 泄漏、大量 Native Bitmap |
| FD 耗尽 | 文件描述符超过限制(1024) | 未关闭的流、数据库连接 |
| 线程 OOM | 线程数超过限制 | 创建过多线程(无限线程池) |
| 虚拟内存 OOM | 32位进程地址空间耗尽 | 大量 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 可能是:
- 内存泄漏:GC Root 持有不该存活的对象
- 内存溢出:单次分配超大对象(如超大 Bitmap)
- 资源泄漏:FD 耗尽、线程耗尽
- 内存抖动:频繁分配短期对象导致碎片化
Q2: 如何预防 OOM?
答案:
- 使用图片加载库(Coil/Glide),避免手动加载大 Bitmap
- 使用
try-with-resources/use确保释放资源 - 使用线程池/协程而非裸线程
- 集合使用完及时
clear() - 使用
onTrimMemory()响应系统内存警告 - 大数据量使用
LruCache控制缓存上限