因很多小内存堆积导致的内存溢出: 比如大量小图片+大量小数据
项目中: 社交应用图片标签缓存泄漏
1.多图片的demo
public class OomQueueImageActivity extends AppCompatActivity {
private static final String TAG = "OomQueueImageActivity";
private ImageView mPhotoView;
private ImageView mPhotoBgView;
private TextView textView;
private int currentSize = 500; // 初始尺寸
private int increment = 10; // 每次增加的尺寸
@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) {
Log.d(TAG,"onClick");
// 使用动态尺寸加载图片
Bitmap bitmap1 = decodeSampledBitmapFromResource(
getResources(), R.mipmap.smart, currentSize, currentSize);
Bitmap bitmap2 = decodeSampledBitmapFromResource(
getResources(), R.mipmap.big, currentSize, currentSize);
Bitmap bitmap3 = decodeSampledBitmapFromResource(
getResources(), R.mipmap.biga, currentSize, currentSize);
// 每次点击增加图片尺寸
currentSize += increment;
putPhotoCache(bitmap1);
putPhotoCache(bitmap2);
putPhotoCache(bitmap3);
}
});
}
// 将Bitmap添加到静态缓存
private void putPhotoCache(Bitmap bitmap) {
sImageCache.add(bitmap); // 错误3:静态集合会一直持有Bitmap引用
}
// 从资源加载合适尺寸的Bitmap
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析只获取尺寸信息
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 计算采样率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 实际加载Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
// 计算采样率
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// 计算满足要求的最小采样率(2的幂)
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
内存增长过程
用户每次点击按钮:
- 加载3张1000x1000的大图 → 约12MB Native内存
- 将Bitmap加入静态集合 → Java层引用
2.用KOOM分析
2.1 Koom的报告
{
"runningInfo": {
"osVersion": "Android 13",
"deviceModel": "Pixel 6 Pro",
"appVersion": "1.0.0",
"heapSize": "256 MB",
"memoryStatus": "CRITICAL",
"analysisTime": "2023-08-15T14:30:45Z",
"gcCount": 42,
"gcTime": "15.2s"
},
"gcPaths": [
{
"gcRoot": "Static field: OomQueueImageActivity.sImageCache",
"chain": [
"java.util.ArrayList (OomQueueImageActivity.sImageCache)",
"java.lang.Object[] (ArrayList.elementData)",
"android.graphics.Bitmap (array element)"
],
"gcRootType": "STATIC_FIELD"
}
],
"leakObjects": [
{
"object": "android.graphics.Bitmap@e3a7d801",
"size": "23.84 MB",
"dimensions": "2500x2500",
"allocationStack": [
"OomQueueImageActivity.decodeSampledBitmapFromResource()",
"OomQueueImageActivity.access$000()",
"OomQueueImageActivity$1.onClick()"
]
},
{
"object": "android.graphics.Bitmap@f4b32c9a",
"size": "23.84 MB",
"dimensions": "2500x2500",
"allocationStack": [
"OomQueueImageActivity.decodeSampledBitmapFromResource()",
"OomQueueImageActivity.access$000()",
"OomQueueImageActivity$1.onClick()"
]
},
{
"object": "android.graphics.Bitmap@c7d229f3",
"size": "23.84 MB",
"dimensions": "2500x2500",
"allocationStack": [
"OomQueueImageActivity.decodeSampledBitmapFromResource()",
"OomQueueImageActivity.access$000()",
"OomQueueImageActivity$1.onClick()"
]
}
],
"classInfos": {
"android.graphics.Bitmap": {
"instanceCount": 600,
"totalSize": "12.58 GB",
"shallowSize": "48 bytes/instance",
"retainedSize": "23.84 MB/instance"
},
"java.util.ArrayList": {
"instanceCount": 1,
"totalSize": "24 KB",
"retainedSize": "12.58 GB",
"fields": [
{
"name": "elementData",
"type": "java.lang.Object[]",
"value": "size=600"
}
]
},
"OomQueueImageActivity": {
"staticFields": [
{
"name": "sImageCache",
"type": "java.util.ArrayList",
"value": "ArrayList@d8a3f1c"
}
]
}
},
"memorySummary": {
"totalMemory": "1.4 GB",
"nativeHeap": "200 MB",
"javaHeap": "1.2 GB",
"bitmapMemory": "1.18 GB",
"activityCount": 1,
"fragmentCount": 0,
"viewCount": 3
},
"leakStatistics": {
"leakCount": 600,
"totalLeakSize": "12.58 GB",
"largestLeakGroup": {
"signature": "Static field → ArrayList → Bitmap",
"count": 600,
"size": "12.58 GB"
}
}
}]
}
2.2 Koom的报告分析
2.2.1. runningInfo:
gcCount
: 42次(频繁GC表明内存压力大)gcTime
: 15.2秒(GC耗时过长影响性能)memoryStatus
: CRITICAL(内存已达临界状态)
2.2.2. gcPaths:
"gcRoot": "Static field: OomQueueImageActivity.sImageCache",
"chain": [
"java.util.ArrayList (OomQueueImageActivity.sImageCache)",
"java.lang.Object[] (ArrayList.elementData)",
"android.graphics.Bitmap (array element)"
]
- 泄漏路径:静态变量 → ArrayList → Object数组 → Bitmap对象
2.2.3. leakObjects:
- 每个Bitmap约23.84MB(2500x2500像素)
- Allocation Stack显示分配位置在`decodeSampledBitmapFromResource()`
- 共600个泄漏Bitmap(200次点击 × 每次3张)
2.2.4. classInfos:
- `android.graphics.Bitmap`:
- `instanceCount: 600`(共600个实例)
- `totalSize: "12.58 GB"`(总占用内存)
- `retainedSize: "23.84 MB/instance"`(单张内存)
- `java.util.ArrayList`:
- `retainedSize: "12.58 GB"`(该集合持有全部Bitmap内存)
- `OomQueueImageActivity`:
- 静态字段`sImageCache`持有ArrayList
3.用profiler分析
显示的是nativie的内存比较大!
3.1 堆转储分析 (Heap Dump)
-
在内存峰值时点击 Dump Java heap 按钮
-
在堆转储视图中:
// 过滤关键类 Filter: android.graphics.Bitmap Filter: OomQueueImageActivity
-
分析 Bitmap 实例:
- 按 Retained Size 降序排序
- 检查大尺寸 Bitmap (2500x2500)
查看内存分布:
- 点击 Arrange by package 分组
- 展开
android.graphics
包 - 查看所有 Bitmap 实例的 Shallow Size 和 Retained Size
3.2 引用链追踪
- 右键点击 Bitmap → Jump to Source → 定位到
putPhotoCache()
方法 - 查看 References 面板确认静态引用链
3.3. 查看单个 Bitmap 大小:
-
在对象列表中选中一个 Bitmap
-
查看右侧属性面板:
// 关键属性 mWidth: 2500 mHeight: 2500 mByteCount: 250025004 = 25,000,000 bytes (23.84 MB)
4.用Mat分析
直方图
4.1 报告关键点分析
- 泄漏路径:
- 存在30条泄漏路径,表明有多个Bitmap对象被静态集合持有
- 关键路径显示:OomQueueImageActivity → sImageCache(ArrayList) → elementData(Object数组) → Bitmap对象
- 内存占用:
- 单个Bitmap的Shallow Heap为48字节
- ArrayList的elementData数组占用了3,304字节
- 整个OomQueueImageActivity类保留了9,408字节内存
- 关键问题类:
- com.evenbus.myapplication.leak.OomQueueImageActivity中的静态字段sImageCache持有了Bitmap
目前看一张图片只有 48个字节!
4.2 直方图分析
关键数据:
Class Name | Objects | Shallow Heap | Retained Heap |
---|---|---|---|
android.graphics.Bitmap | 600 | 28,800 B | 14.3 GB |
byte[] | 600 | - | 14.3 GB |
java.util.ArrayList | 1 | 24 B | 14.3 GB |
支配树: 都是不是显示最大的! 这怎么查找?
4.3 支配树分析
先找到大对象,ArryList,然后找到里面存放的bitmap!
- 点击工具栏 Dominator Tree 图标
- 按 Retained Heap 降序排序
- 展开 System Class Loader 节点
操作步骤:
-
右键点击 Bitmap 实例
-
选择 Path to GC Roots → exclude weak references
-
查看完整引用链:
步骤 2:分析集合内容
-
右键 ArrayList → List objects → with outgoing references
-
展开
elementData: Object[600]
-
查看数组元素:
[0]: android.graphics.Bitmap @0x7d3a9100 mWidth = 500 mHeight = 500 mBuffer = byte[1,000,000] [599]: android.graphics.Bitmap @0x7d3aff00 mWidth = 2500 mHeight = 2500 mBuffer = byte[25,000,000]
步骤 3:追踪创建来源
-
选择 Bitmap → Show in Inspector
-
查看对象属性:
allocationTracingId = 48392 creationTime = 12:30:45.123
-
使用 OQL 查询创建堆栈:
SELECT * FROM INSTANCESOF android.graphics.Bitmap WHERE allocationTracingId = 48392
android.graphics.Bitmap @ 0x7d3a9100 |- held by java.lang.Object[600] @ 0x7d3a8f10 (element of ArrayList) |- held by java.util.ArrayList @ 0x7d3a8f00 |- held by static OomQueueImageActivity.sImageCache |- held by class OomQueueImageActivity |- held by system class loader
java.util.ArrayList @ 0x7d3a8f00
|- Retained Heap: 14.3 GB (94.6%)
|- Shallow Heap: 24 B
|- 持有对象:
|- java.lang.Object[600] @ 0x7d3a8f10
|- [0]: android.graphics.Bitmap @ 0x7d3a9100 (23.84 MB)
|- [1]: android.graphics.Bitmap @ 0x7d3a9200 (23.84 MB)
|- ...
|- [599]: android.graphics.Bitmap @ 0x7d3aff00 (23.84 MB)
5. 扩展, 加载大量小数据
private void loadBigData() {
// 模拟加载大量数据
for (int i = 0; i < 10000990; i++) {
dataModel.add("Data item " + i);
}
}
class BigDataModel {
List<String> dataList = new ArrayList<>();
void add(String data) {
dataList.add(data);
}
}
每个Activity占用大量内存:
- 每个Activity包含一个BigDataModel
- BigDataModel中包含大量数据(100,000条字符串)
操作步骤:
- 右键
BigDataModel
实例 - 选择 Path to GC Roots → with all references
- 查看完整引用链
6.总结
方法 1:支配树快速定位(推荐)
操作步骤:
- 打开 MAT → Dominator Tree
- 按 Retained Heap 降序排序
- 展开 System Class Loader
- 检查 TOP 5 支配者对象
关键指标:
- Retained Heap > 1 MB 的对象都值得关注
- 支配比例 > 5% 的对象需要重点分析
示例结果:
对象地址 | 类型 | Retained Heap | 支配比例 | 可疑度 |
---|---|---|---|---|
0x7da3f100 | BigDataModel | 6.4 MB | 85% | ⚠️高 |
0x7da3f110 | ArrayList | 6.4 MB | 85% | ⚠️高 |
0x7da40100 | Activity | 0.8 MB | 10% | 中 |
0x7dc0a000 | Bitmap | 0.5 MB | 6% | 低 |
方法 2:直方图全局扫描
操作步骤:
- 打开 Histogram 视图
- 按 Retained Heap 降序排序
- 右键类名 → List objects → with outgoing references
- 检查大内存类的实例
诊断技巧:
- 字节数组:可能是图片/文件数据
- 对象数组:通常是集合的底层存储
- 字符串:文本数据缓存
- Bitmap:图片资源
方法 3:Top Consumers 报告
操作步骤:
- Reports → Top Consumers
- 选择 By retained size
- 展开 Group by package
典型报告结构:
Top Consumers - Retained Size
---------------------------------
1. com.example (85%)
|- BigDataModel @0x7da3f100: 6.4 MB
| |- ArrayList @0x7da3f110: 6.4 MB
| |- Object[100000]: 400 KB
| |- 100000 String: 6.0 MB
2. android.graphics (6%)
|- Bitmap @0x7dc0a000: 0.5 MB
3. java.util (5%)
|- HashMap @0x7da41200: 0.4 MB
方法 4:OQL 高级查询
查找大对象:
// 查找保留堆 > 1MB 的对象
SELECT * FROM INSTANCESOF java.lang.Object
WHERE @retainedHeapSize > 1048576
查找大集合:
// 查找元素 > 10000 的集合
SELECT * FROM java.util.ArrayList
WHERE arrayLength(elementData) > 10000
查找大数组:
// 查找 > 500KB 的 byte[]
SELECT * FROM byte[] s
WHERE s.@length > 500000
方法 5:Leak Suspects 报告
操作步骤:
- Reports → Leak Suspects
- 查看 Problem Suspects
典型报告:
SUSPECT 1
---------
6.4 MB retained by com.example.BigDataModel @ 0x7da3f100
• 100,000 instances of java.lang.String loaded by system class loader
Accumulated Objects:
• java.lang.String: 100,000 items (6.0 MB)
• java.lang.Object[]: 1 item (400 KB)
• java.util.ArrayList: 1 item (24 B)
排查工具总结表
方法 | 使用场景 | 优势 | 检出率 |
---|---|---|---|
支配树 | 快速定位 | 直观显示内存黑洞 | ★★★★★ |
直方图 | 全局扫描 | 发现异常类分布 | ★★★★☆ |
Top Consumers | 系统报告 | 结构化分析 | ★★★★☆ |
OQL 查询 | 精准定位 | 自定义条件 | ★★★☆☆ |
对比分析 | 增量检查 | 识别变化对象 | ★★★★★ |
Leak Suspects | 自动诊断 | 智能建议 | ★★★☆☆ |
最佳实践建议
-
排查优先级: 图片:
-
关键阈值:
- 单个对象 > 1 MB → 必须检查
- 同类实例 > 1000 → 需要优化
- 对象数组 > 10000 → 可疑集合
7.如何用MAT,排查很多小对象,导致的内存慢慢累积的问题
7.1. 直方图聚合分析(关键步骤)
操作流程:
- 打开 Histogram 视图
- 按 Retained Heap 降序排序
- 按类名分组聚合
- 关注实例数 > 1000 的类
诊断技巧:
// 重点检查这些类:
- java.lang.String
- java.lang.Object[]
- java.util.HashMap$Node
- android.util.SparseArray
- 自定义数据模型类
排查指标:
类名 | 实例数 | 平均大小 | 总保留堆 | 风险等级 |
---|---|---|---|---|
String | 125,000 | 64 B | 8.0 MB | ⚠️高 |
HashMap$Node | 80,000 | 32 B | 2.5 MB | ⚠️中 |
MyItem | 50,000 | 48 B | 2.3 MB | ⚠️高 |
7.2. 支配树分组查看
操作流程:
- 打开 Dominator Tree
- 点击 Group → by class
- 按 Retained Heap 排序
- 展开高实例数的类
分组分析:
com.example.MyItem (50,000 instances)
├─ Total Retained Heap: 2.3 MB
├─ Shallow Size per item: 48 B
└─ Held by:
├─ ArrayList @0x7da3f100 (20,000 items)
└─ HashMap @0x7dc0a000 (30,000 items)
7.3. 增量对比分析(最有效方法)
操作流程:
- 捕获 T1 时刻堆转储(基线)
- 执行操作,等待内存增长
- 捕获 T2 时刻堆转储
- MAT 中:Compare to another heap dump
关键操作:
- 在对比结果中右键增量对象
- 选择 Show Objects by Class
- 分析新增对象的引用链
项目源码的地址:github.com/pengcaihua1…