OOM(Out Of Memory)是Android开发中最棘手的问题之一,下面我将从原理到实践全面解析如何避免OOM异常。
一、OOM的本质与分类
1. OOM产生原理
-
当Java堆内存分配失败且GC后仍无法获得足够内存时抛出
-
Android设备有严格的进程内存限制(通常24MB-512MB不等)
-
常见报错:
java.lang.OutOfMemoryError: Failed to allocate...
2. OOM主要类型
类型 | 常见场景 | 特点 |
---|---|---|
Java堆OOM | 大图片加载、对象累积 | 最常见类型 |
线程OOM | 创建过多线程 | pthread_create failed |
FD OOM | 文件描述符耗尽 | Could not allocate JNI Env |
Native OOM | Native内存分配失败 | 较难排查 |
二、核心规避策略
1. 图片处理优化(最主要OOM来源)
Bitmap加载最佳实践:
// 使用inSampleSize进行采样压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, R.id.myimage, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, R.id.myimage, options);
// 使用RGB_565减少内存(质量较低)
options.inPreferredConfig = Bitmap.Config.RGB_565;
// 及时回收
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
推荐图片加载库:
-
Glide:自动处理Bitmap回收和缓存
-
Picasso:简洁的图片加载方案
-
Fresco:使用Native内存的进阶方案
2. 内存缓存优化
三级缓存架构:
-
活动缓存(强引用,LRU)
-
软引用缓存(内存不足时回收)
-
磁盘缓存(持久化存储)
// 使用LruCache int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; // 使用1/8的堆内存 LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; } };
3. 对象池技术
典型实现:
public class ObjectPool<T> {
private final Queue<T> pool = new LinkedList<>();
private final Creator<T> creator;
public interface Creator<T> {
T create();
}
public ObjectPool(Creator<T> creator) {
this.creator = creator;
}
public T obtain() {
synchronized (pool) {
return pool.isEmpty() ? creator.create() : pool.poll();
}
}
public void recycle(T obj) {
synchronized (pool) {
pool.offer(obj);
}
}
}
// 使用示例
ObjectPool<Bitmap> bitmapPool = new ObjectPool<>(() -> Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
4. 内存泄漏防治(见前文深度解析)
关键点:
-
避免Activity被静态对象引用
-
正确使用Handler
-
及时释放资源
5. 大对象拆分策略
示例:大数组处理
// 不推荐:一次性加载大数组
byte[] hugeData = new byte[50 * 1024 * 1024];
// 推荐:分块处理
int chunkSize = 1024 * 1024; // 1MB
for (int i = 0; i < totalSize; i += chunkSize) {
int size = Math.min(chunkSize, totalSize - i);
byte[] chunk = new byte[size];
// 处理chunk...
}
6. 优化数据结构
选择建议:
-
大量数据:考虑SparseArray替代HashMap<Integer, Object>
-
数据集合:使用ArrayMap替代HashMap
-
枚举:使用@IntDef/@StringDef替代enum
三、高级优化技术
1. 多进程架构
<activity android:name=".ImageGalleryActivity"
android:process=":image_process"/>
-
将内存密集型组件放到独立进程
-
进程崩溃不影响主进程
-
需要处理跨进程通信
2. Native内存管理
-
对于大型计算任务使用Native代码
-
通过JNI管理Native内存
-
注意Native内存泄漏
3. 内存紧张时的处理
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_MODERATE) {
// 释放非关键资源
clearCaches();
}
}
四、监控与诊断体系
1. 线上监控指标
-
内存使用百分位值(P50/P90/P99)
-
OOM发生率和场景
-
大对象分配追踪
2. 诊断工具链
工具 | 适用场景 |
---|---|
Android Profiler | 实时内存分析 |
MAT | 堆转储深度分析 |
LeakCanary | 内存泄漏检测 |
adb shell dumpsys meminfo | 进程内存概况 |
3. 自动化测试方案
// Gradle配置内存测试
android {
testOptions {
unitTests.all {
jvmArgs '-XX:MaxHeapSize=64m' // 模拟低内存设备
}
}
}
五、特殊场景处理
1. 大图加载方案
-
使用SubsamplingScaleImageView等专业控件
-
分块加载和显示
-
后台预处理图片
2. 视频处理
-
使用SurfaceView替代TextureView
-
流式处理避免全量加载
-
使用MediaCodec进行低层编解码
3. WebView优化
// 独立进程
<activity android:name=".WebActivity"
android:process=":webview_process"/>
// 内存配置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
六、架构级预防措施
-
模块化设计:按需加载功能模块
-
懒加载策略:延迟非关键资源初始化
-
资源分级:区分必须资源和可选资源
-
兜底机制:内存不足时降级体验