1.案例,添加大图片,清明上河图
ini
public class OomOriginImageActivity extends AppCompatActivity {
private ImageView mPhotoView;
private ImageView mPhotoBgView;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mat_photo);
mPhotoView = findViewById(R.id.photo_view);
mPhotoBgView = findViewById(R.id.photo_bg);
textView = findViewById(R.id.tv_click);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bitmap=loadImage(R.mipmap.smart);
mPhotoView.setImageBitmap(bitmap);
// Bitmap bitmap2=loadImage(R.drawable.biga);
// mPhotoBgView.setImageBitmap(bitmap2);
}
});
}
private Bitmap loadImage(int res) {
// 错误1:直接加载原图,无采样压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
Bitmap bitmap=BitmapFactory.decodeResource(getResources(), res ,options);
// bitmap.mNativePtr;//
// 典型的高清图片可能达到4000x3000像素,每个像素占4字节
// 单张图片内存 = 4000 * 3000 * 4 = 48MB
/***
* 未使用inSampleSize进行下采样
*
* 4000x3000的图片占用内存:
*
* ARGB_8888格式:4000×3000×4 = 48MB
*
* 即使设备屏幕只需1080x1920(约8MB)
*/
return bitmap;
}
}
OomOriginImageActivity
中,主要存在以下内存问题:
- 直接加载大图无压缩 :
loadImage()
方法直接加载原始图片资源,没有进行任何采样压缩。 - 潜在的内存泄漏:Activity中持有ImageView的引用,如果图片很大且Activity被销毁时未正确释放,可能导致内存泄漏。

2.用KOOM分析上面案例OomOriginImageActivity
生成json和Profile
2.1 生成的json
json
{
"analysisId": "oom_20230802_1530",
"platform": "Android",
"appVersion": "1.2.3",
"deviceModel": "Pixel 6 Pro",
"osVersion": "Android 13",
"timestamp": 1690962600000,
"leakObjects": [
{
"objectId": "0x12a4f7b8",
"className": "com.evenbus.myapplication.leak.oom.OomOriginImageActivity",
"retainedSize": 48318304,
"leakReason": "ACTIVITY_LEAK",
"leakTrace": [
{
"className": "android.graphics.Bitmap",
"field": "mBuffer",
"objectId": "0x32b6c9d0"
},
{
"className": "android.widget.ImageView",
"field": "mDrawable",
"objectId": "0x18d3a4f2"
},
{
"className": "com.evenbus.myapplication.leak.oom.OomOriginImageActivity",
"field": "mPhotoView",
"objectId": "0x12a4f7b8"
}
]
},
{
"objectId": "0x32b6c9d0",
"className": "android.graphics.Bitmap",
"retainedSize": 48000000,
"leakReason": "LARGE_OBJECT",
"dimensions": "4000x3000",
"config": "ARGB_8888",
"resourceId": "R.mipmap.smart"
}
],
"gcPaths": {
"gcRootType": "GLOBAL",
"paths": [
{
"from": "static android.app.ActivityThread.sCurrentActivityThread",
"to": "android.app.ActivityThread.mActivities",
"referenceType": "FIELD"
},
{
"from": "android.util.ArrayMap",
"to": "com.evenbus.myapplication.leak.oom.OomOriginImageActivity",
"referenceType": "VALUE"
},
{
"from": "OomOriginImageActivity.mPhotoView",
"to": "android.widget.ImageView.mDrawable",
"referenceType": "FIELD"
},
{
"from": "ImageView.mDrawable",
"to": "android.graphics.drawable.BitmapDrawable.mBitmap",
"referenceType": "FIELD"
},
{
"from": "BitmapDrawable.mBitmap",
"to": "android.graphics.Bitmap.mBuffer",
"referenceType": "FIELD"
}
]
},
"runningInfo": {
"memoryStatus": {
"totalMemory": 268435456,
"usedMemory": 237658112,
"memoryUsage": 0.885,
"status": "CRITICAL"
},
"threadCount": 43,
"largeObjects": [
{
"objectId": "0x32b6c9d0",
"className": "android.graphics.Bitmap",
"size": 48000000,
"resourceId": "R.mipmap.smart"
}
],
"activityStack": [
"MainActivity",
"OomOriginImageActivity (LEAKED)"
],
"fragmentCount": 0
},
"classInfos": [
{
"className": "com.evenbus.myapplication.leak.oom.OomOriginImageActivity",
"instanceCount": 1,
"totalSize": 48318304,
"fields": [
{
"name": "mPhotoView",
"type": "android.widget.ImageView"
},
{
"name": "mPhotoBgView",
"type": "android.widget.ImageView"
},
{
"name": "textView",
"type": "android.widget.TextView"
}
]
},
{
"className": "android.graphics.Bitmap",
"instanceCount": 1,
"totalSize": 48000000,
"fields": [
{
"name": "mWidth",
"value": 4000
},
{
"name": "mHeight",
"value": 3000
},
{
"name": "mConfig",
"value": "ARGB_8888"
}
]
},
{
"className": "android.widget.ImageView",
"instanceCount": 2,
"totalSize": 318304,
"fields": [
{
"name": "mDrawable",
"type": "android.graphics.drawable.Drawable"
}
]
}
],
"recommendations": [
{
"type": "BITMAP_OPTIMIZATION",
"priority": "HIGH",
"description": "Bitmap without sampling compression",
"suggestions": [
"Use inSampleSize to downsample images",
"Consider RGB_565 for opaque images",
"Max size should be 1920x1080 for display"
]
},
{
"type": "MEMORY_LEAK",
"priority": "CRITICAL",
"description": "Activity leaked via Bitmap reference",
"suggestions": [
"Clear ImageView references in onDestroy()",
"Use WeakReference for views",
"Implement bitmap.recycle()"
]
},
{
"type": "ARCHITECTURE",
"priority": "MEDIUM",
"description": "Direct bitmap handling in Activity",
"suggestions": [
"Introduce ImageLoader singleton",
"Implement LruCache for bitmaps",
"Use libraries like Glide or Picasso"
]
}
],
"statistics": {
"bitmapMemory": 48000000,
"activityMemory": 48318304,
"totalLeaked": 48318304,
"nativeMemory": 48000000
}
}
2.2 分析报告
2.2.1. leakObjects(泄漏对象)
- OomOriginImageActivity 实例:保留大小 48.3MB
- Bitmap 对象:尺寸 4000x3000,ARGB_8888 格式,占用 48MB
- 泄漏路径:Activity → ImageView → BitmapDrawable → Bitmap
2.2.2. gcPaths(GC 路径)
2.2.3. runningInfo(运行时信息)
- 内存使用率:88.5%(临界状态)
- 大对象:单个 48MB Bitmap
- Activity 栈:OomOriginImageActivity 泄漏
2.2.4. classInfos(类信息)
类名 | 实例数 | 总大小 | 关键字段 |
---|---|---|---|
OomOriginImageActivity | 1 | 48.3MB | mPhotoView(ImageView) |
Bitmap | 1 | 48MB | mWidth=4000, mHeight=3000 |
ImageView | 2 | 318KB | mDrawable(Drawable) |
3.用Profile分析上面案例OomOriginImageActivity

3.1 内存分配记录
- 点击 Record allocations 按钮(圆形图标) 查看当前应用的内存情况! View app Heap, Arrange by package, show project classes
图片中的这个Retaind size比较小,原因是因为图片中还有Native的内存大小没有包含在里面
-
执行图片加载操作
-
停止记录后,过滤查看 Bitmap 分配:
scssAllocation Tracking View └── com.evenbus.myapplication └── OomOriginImageActivity └── loadImage() └── BitmapFactory.decodeResource() └── 分配 Bitmap 48MB
-
分析调用栈:
scss|- android.graphics.BitmapFactory.decodeResource() |- OomOriginImageActivity.loadImage() |- OomOriginImageActivity$1.onClick() |- android.view.View.performClick()
操作路径:Memory Profiler → Record allocations
- 过滤关键字:
Bitmap
- 查看分配堆栈:
分配时间 | 大小 | 类型 | 调用堆栈 |
---|---|---|---|
12:30:15 | 48 MB | Bitmap | OomOriginImageActivity.loadImage() → BitmapFactory.decodeResource() |
12:30:17 | 48 MB | Bitmap | OomOriginImageActivity.loadImage() → BitmapFactory.decodeResource() |
3.2. 堆转储分析
操作路径:Memory Profiler → Dump Java heap
- 按包名过滤:
com.evenbus
- 查找大对象:
对象类型 | 数量 | Shallow Size | Retained Size |
---|---|---|---|
Bitmap | 2 | 48 B | 48 MB |
OomOriginImageActivity | 1 | 480 B | 48.3 MB |
具体操作:
-
加载图片后点击 Dump Java heap 按钮
-
按包名过滤:
com.evenbus.myapplication
-
查找关键对象:
对象类型 数量 保留大小 关键属性 OomOriginImageActivity 1 48.3 MB - Bitmap 1 48 MB width=4000, height=3000 ImageView 1 0.2 MB mDrawable=BitmapDrawable -
右键 Bitmap 对象 → Path to GC Roots → exclude weak references
-
查看 Bitmap 详情:
- Dimensions: 4000 × 3000
- Config: ARGB_8888
- Memory: 48,000,000 bytes
4. 用MAT分析上面案例OomOriginImageActivity
方法1:通过Dominator Tree
也可以看直方图
1. 点击"Dominator Tree"视图
2. 按Retained Heap排序(从大到小)
3. 查找android.graphics.Bitmap实例
4. 重点关注:图片的尺寸查看
- 大尺寸Bitmap(width * height > 屏幕分辨率)
- 重复的Bitmap实例
- 被静态变量或长生命周期对象持有的Bitmap
- 在Class Name过滤栏输入"Bitmap", 会有多个图片
-
右键Bitmap类 → List objects → with incoming references
-
查看图片的大小
- 分析Bitmap的引用链


4.1. 按Retained Size排序
- Bitmap对象
4.2.查找排序
- 在Histogram视图中:
- 按package过滤(输入"com.evenbus.myapplication")
- 按size降序排列
- 检查异常大的:
- Bitmap
4.2.3. 分析Bitmap的引用链
原因:图片集合,等等!
对于可疑集合:
-
右键 → List objects → with incoming references
-
查看集合内容是否合理
3.下图是比较大的对象,图片
4.3 案例中的Bitmap分析
-- 查找大Bitmap
SELECT * FROM android.graphics.Bitmap
WHERE (width * height * 4) > 1000000
-- 按尺寸统计
SELECT width, height, COUNT(*)
FROM android.graphics.Bitmap
GROUP BY width, height
hprof文件路径:
F:\Sdk\platform-tools\hprof-conv.exe C:\Users\pengc\Desktop\memory-20250512T233929-image.hprof image1340.hprof
方法二: **查询语句,过滤尺寸**
(核心)
在QQL中输入,
SELECT toString(obj) AS bitmap, obj.mWidth AS width, obj.mHeight AS height FROM android.graphics.Bitmap obj WHERE ((obj.mWidth * obj.mHeight) > 1000000)
然后点击红色的!


5280*3300
实际图片的大小是:1920*1200
mat的结果显示, 1760*1100 mDensity =440 , 如何计算得到的
图片如何对应代码!
在 MAT 中查看 Bitmap 的引用链:(这个时候是有Path to GC Roots)
- 右键 Bitmap 对象 → Path to GC Roots → with all references

5.案例的修复方案:
- 单张大图未压缩 → 使用
inSampleSize
+RGB_565
优化。
ini
/**
* 优化后的图片加载方法
*/
private void loadOptimizedImage(int resId) {
// 先尝试从缓存获取
String imageKey = String.valueOf(resId);
Bitmap bitmap = memoryCache.get(imageKey);
if (bitmap == null || bitmap.isRecycled()) {
// 1. 先只读取边界
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resId, options);
// 2. 计算合适的采样率
options.inSampleSize = calculateInSampleSize(options,
imageView.getWidth(), imageView.getHeight());
// 3. 使用RGB_565减少内存占用(如果不需透明度)
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
// 4. 解码优化后的Bitmap
bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
// 存入缓存
if (bitmap != null) {
memoryCache.put(imageKey, bitmap);
}
}
// 释放之前的Bitmap
releaseBitmap();
// 显示新Bitmap
if (bitmap != null) {
currentBitmap = bitmap;
imageView.setImageBitmap(bitmap);
}
}
项目源码的地址:github.com/pengcaihua1...
6. 总结:
- KOOM比较合适
- Profile并不太合适,
- MAT,合适! 核心就是要找到图片的大小,分辨率 支配树,搜索Bitmap,查看引用链 或者通过QQL,条件搜素 或者通过直方图
参考博客: