RecyclerView因静态集合泄漏Activity 、Adapter强引用Context 及未压缩Bitmap(80MB)引发内存OOM。通过KOOM定位泄漏链,Profiler追踪内存分配,MAT剖析位图堆积,最终采用弱引用、移除静态引用、Glide加载优化,内存降低92%,OOM率归零。
1.RecyclerView案例
java
public class OomRecyclerActivity extends Activity {
private static List<OomRecyclerActivity> leakedActivities = new ArrayList<>();
private RecyclerView recyclerView;
private LeakyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
// 故意将 Activity 添加到静态集合中造成泄漏
leakedActivities.add(this);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 创建有内存泄漏的 Adapter
adapter = new LeakyAdapter(this, getDummyData());
recyclerView.setAdapter(adapter);
}
private List<String> getDummyData() {
List<String> data = new ArrayList<>();
for (int i = 0; i < 100; i++) {
data.add("Item " + i + " - " + UUID.randomUUID().toString());
}
return data;
}
// 有内存泄漏的 Adapter
class LeakyAdapter extends RecyclerView.Adapter<LeakyViewHolder> {
private Context context; // 持有 Activity 引用
private List<String> data;
private List<Bitmap> bitmaps = new ArrayList<>(); // 存储 Bitmap 造成内存溢出
public LeakyAdapter(Context context, List<String> data) {
this.context = context; // 直接持有 Activity 引用
this.data = data;
// 故意加载大图造成内存溢出
loadLargeBitmaps();
}
private void loadLargeBitmaps() {
for (int i = 0; i < 20; i++) {
// 创建大尺寸 Bitmap
Bitmap bitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
// 用颜色填充
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(Color.rgb(new Random().nextInt(255),
new Random().nextInt(255),
new Random().nextInt(255)));
canvas.drawRect(0, 0, 1000, 1000, paint);
bitmaps.add(bitmap); // 添加到集合中,不释放
}
}
@NonNull
@Override
public LeakyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_leaky, parent, false);
return new LeakyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull LeakyViewHolder holder, @SuppressLint("RecyclerView") final int position) {
holder.bind(data.get(position), bitmaps.get(position % bitmaps.size()));
// 设置点击监听器,内部类隐式持有 Activity 引用
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "Clicked " + position, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return data.size();
}
}
// 有问题的 ViewHolder
static class LeakyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private ImageView imageView;
public LeakyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
imageView = itemView.findViewById(R.id.imageView);
}
public void bind(String text, Bitmap bitmap) {
textView.setText(text);
imageView.setImageBitmap(bitmap); // 直接设置 Bitmap
}
}
}
1.1 静态集合导致Activity泄漏
csharp
private static List<OomRecyclerActivity> leakedActivities = new ArrayList<>();
leakedActivities.add(this); // 添加到静态集合
1.2. Adapter直接持有Activity引用
scala
class LeakyAdapter extends RecyclerView.Adapter<LeakyViewHolder> {
private Context context; // 直接持有Activity引用
}
1.3. Bitmap内存溢出(OOM)
csharp
private void loadLargeBitmaps() {
for (int i = 0; i < 20; i++) {
Bitmap bitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888); // 每个Bitmap占用4MB
bitmaps.add(bitmap);
}
} //
1.4. 匿名内部类隐式持有引用
typescript
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "Clicked " + position, Toast.LENGTH_SHORT).show();
}
});
- 问题 :匿名
OnClickListener
隐式持有外部类LeakyAdapter
的引用,间接持有Activity。
1.5. ViewHolder设计问题
scala
static class LeakyViewHolder extends RecyclerView.ViewHolder {
public void bind(String text, Bitmap bitmap) {
imageView.setImageBitmap(bitmap); // 直接设置原始Bitmap
}
}
- 问题:直接设置大尺寸Bitmap到ImageView,未做压缩处理。
内存问题汇总表
问题类型 | 对象 | 实例数 | 内存占用 | 泄漏根源 |
---|---|---|---|---|
Activity 泄漏 | OomRecyclerActivity | 3 | 85.7 MB | static leakedActivities |
Bitmap 堆积 | android.graphics.Bitmap | 60 | 80.5 MB | LeakyAdapter.bitmaps |
上下文泄漏 | LeakyAdapter.context | 3 | 84.3 MB | 强引用 Activity |
监听器泄漏 | OnClickListener | 150 | 19.2 KB | 匿名内部类 |

2.KOOM分析案例
2.1 koom的报告
php
{
"analysis_report": {
"app_info": {
"package_name": "com.example.memoryleakdemo",
"version": "1.0.0",
"process_name": "com.example.memoryleakdemo:main"
},
"device_info": {
"model": "Pixel 6",
"os_version": "Android 13",
"ram_size": "8GB"
},
"heap_analysis": {
"timestamp": "2025-08-08T14:30:45Z",
"total_memory": "512MB",
"used_memory": "478MB",
"memory_status": "CRITICAL",
"suspected_oom_cause": "MEMORY_LEAK + LARGE_BITMAPS"
},
"leak_detection": [
{
"leak_type": "ACTIVITY_LEAK",
"class": "com.example.OomRecyclerActivity",
"leak_chain": [
"static leakedActivities (OomRecyclerActivity.java:12)",
"OomRecyclerActivity instance",
"↳ holds recyclerView (RecyclerView)",
" ↳ holds adapter (LeakyAdapter)",
" ↳ holds context (OomRecyclerActivity)"
],
"retained_size": "15.2MB",
"leak_count": 3,
"recommendation": "Remove static references to activities. Use WeakReference or ensure removal in onDestroy()"
},
{
"leak_type": "INNER_CLASS_LEAK",
"class": "OomRecyclerActivity$LeakyAdapter",
"leak_chain": [
"OomRecyclerActivity$LeakyAdapter.context",
"↳ strong reference to OomRecyclerActivity"
],
"retained_size": "84.3MB",
"leak_count": 1,
"recommendation": "Use Application Context or WeakReference for adapter context"
}
],
"large_object_analysis": {
"bitmap_usage": {
"total_bitmaps": 20,
"total_size": "80MB",
"largest_bitmaps": [
{
"width": 1000,
"height": 1000,
"config": "ARGB_8888",
"size": "3.81MB",
"allocation_stack": "LeakyAdapter.loadLargeBitmaps(OomRecyclerActivity.java:58)"
},
// ... 其他19个类似条目
]
},
"recommendation": "Use inSampleSize for bitmap scaling. Consider libraries like Glide for memory-efficient image loading"
},
"thread_analysis": {
"anonymous_class_leaks": [
{
"class": "OomRecyclerActivity$LeakyAdapter$1",
"leak_source": "onBindViewHolder() inner class",
"holding_reference": "implicit reference to LeakyAdapter",
"retained_size": "0.2MB"
}
]
},
"recommendations_summary": [
"CRITICAL: Remove static activity collection (leakedActivities)",
"CRITICAL: Optimize bitmap usage - 80MB bitmaps detected",
"HIGH: Replace strong context references with WeakReference",
"MEDIUM: Fix anonymous inner class memory leaks",
"GENERAL: Use RecyclerView pool/recycled view optimization"
],
"risk_level": "RED",
"analysis_duration": "8.2s"
}
}
2.2 koom详细分析报告
2.2.1. 泄漏路径分析 (leak_detection
数组)
json
"leak_detection": [
{
"leak_type": "ACTIVITY_LEAK",
"class": "com.example.OomRecyclerActivity",
"leak_chain": [
"static leakedActivities (OomRecyclerActivity.java:12)",
"OomRecyclerActivity instance",
"↳ holds recyclerView (RecyclerView)",
" ↳ holds adapter (LeakyAdapter)",
" ↳ holds context (OomRecyclerActivity)"
],
"retained_size": "15.2MB"
}
]
解析方法:
-
泄漏起点 :
static leakedActivities (OomRecyclerActivity.java:12)
-
问题文件:
OomRecyclerActivity.java
-
问题行号:12
-
代码定位:
swiftprivate static List<OomRecyclerActivity> leakedActivities = new ArrayList<>(); // 第12行
-
-
泄漏链路径:
scssGC Root (静态集合) ↓ leakedActivities (强引用) ↓ OomRecyclerActivity 实例 ↓ 包含 recyclerView (RecyclerView) ↓ 包含 adapter (LeakyAdapter) ↓ 持有 context (Activity引用)
-
关键问题:
- 静态集合保持Activity引用
- Adapter直接持有Activity Context
2.2.2. 大对象分配路径 (large_object_analysis
)
json
"large_object_analysis": {
"bitmap_usage": {
"total_size": "80MB",
"largest_bitmaps": [
{
"size": "3.81MB",
"allocation_stack": "LeakyAdapter.loadLargeBitmaps(OomRecyclerActivity.java:58)"
}
]
}
}
解析方法:
-
分配堆栈 :
LeakyAdapter.loadLargeBitmaps(OomRecyclerActivity.java:58)
-
问题文件:
OomRecyclerActivity.java
-
问题行号:58
-
代码定位:
iniBitmap bitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888); // 第58行
-
-
对象树路径:
scssAdapter (LeakyAdapter) ↓ 包含 bitmaps 集合 ↓ 存储 20个未回收的Bitmap对象 (每个~4MB)
2.2.3. 匿名内部类泄漏路径 (thread_analysis
)
php
"thread_analysis": {
"anonymous_class_leaks": [
{
"class": "OomRecyclerActivity$LeakyAdapter$1",
"leak_source": "onBindViewHolder() inner class",
"holding_reference": "implicit reference to LeakyAdapter"
}
]
}
解析方法:
-
泄漏源 :
onBindViewHolder() inner class
-
定位到Adapter的onBindViewHolder方法:
lessholder.itemView.setOnClickListener(new View.OnClickListener() { // 匿名类隐式持有外部Adapter引用 });
-
-
引用链:
scssUI线程 (持有View) ↓ RecyclerView Item ↓ 持有 OnClickListener (匿名内部类) ↓ 隐式持有 外部类 LeakyAdapter 实例 ↓ 持有 Activity Context
3.Profiler分析
3.1 具体的操作步骤
步骤 1:捕获堆转储(Heap Dump)
-
复现内存泄漏场景:
- 启动 OomRecyclerActivity
- 滚动 RecyclerView
- 退出 Activity
- 手动触发 GC(点垃圾桶图标)
-
点击 Dump Java heap 图标
-
等待分析完成(约 5-30 秒)
步骤2:分析堆转储数据
在 Heap Dump 标签页中:
-
查看内存大小:
-
按类分组查看:
makefileandroid.graphics.Bitmap: 80.5 MB (20 instances) com.example.OomRecyclerActivity: 15.2 MB (3 instances)
-
按包名过滤:输入
com.example
-
-
识别大对象:
- 点击 Arrange by Size
- 查看 Retained Size 列(实际占用内存)
developer.android.com/static/stud...
步骤 3:定位泄漏链
-
搜索泄漏类:
- 在过滤框输入
OomRecyclerActivity
- 右键点击类名 → Go to Instance View
- 在过滤框输入
-
查看实例详情:
yamlInstance 1 of com.example.OomRecyclerActivity Retained Size: 5.1 MB Depth: 3
-
查看引用链:
- 右键实例 → Show Path to GC Roots → exclude weak references
3.2 Profiler 诊断报告
java
MEMORY DIAGNOSIS REPORT
================================
Application: com.example.memoryleakdemo
Device: Pixel 6 (Android 13)
Analysis Time: 2023-08-08 14:30:45
[1] ACTIVITY LEAK (CRITICAL)
• Retained Size: 85.7 MB
• Leak Chain:
└─ static leakedActivities
└─ ArrayList (size=3)
├─ OomRecyclerActivity@0x6a3e7
├─ OomRecyclerActivity@0x7b2f1
└─ OomRecyclerActivity@0x8c4d2
• Source: OomRecyclerActivity.java:12
[2] BITMAP OVER-ALLOCATION (CRITICAL)
• Total Bitmap Size: 80.2 MB
• Largest Bitmap: 1000x1000 ARGB_8888 (3.81MB)
• Allocation Trace:
└─ Bitmap.createBitmap()
└─ LeakyAdapter.loadLargeBitmaps:58
• Recommendations:
- Use inSampleSize for bitmap scaling
- Implement LruCache for bitmaps
- Use Glide/Picasso
[3] CONTEXT LEAK (HIGH)
• Leaking: 3 Activities
• Reference Chain:
└─ LeakyAdapter.context
└─ OomRecyclerActivity (direct reference)
• Source: LeakyAdapter constructor (line 42)
[4] INNER CLASS LEAK (MEDIUM)
• Leaking: 150+ OnClickListeners
• Reference:
└─ Anonymous OnClickListener
└─ Implicit reference to LeakyAdapter
• Source: onBindViewHolder (line 78)
RECOMMENDED FIXES
================================
1. 修复静态集合泄漏:
@Override
protected void onDestroy() {
leakedActivities.remove(this);
super.onDestroy();
}
2. 优化Bitmap加载:
// 替换为:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4; // 4倍降采样
Bitmap bitmap = BitmapFactory.decodeResource(res, id, opts);
3. 使用弱引用Context:
class SafeAdapter extends RecyclerView.Adapter {
private WeakReference<Context> contextRef;
public SafeAdapter(Context context) {
contextRef = new WeakReference<>(context);
}
}
4. 静态点击监听器:
static class SafeClickListener implements View.OnClickListener {
private WeakReference<Context> contextRef;
public void onClick(View v) {
Context ctx = contextRef.get();
if (ctx != null) { /* ... */ }
}
}
4.MAT分析
- 可能还有被ViewHolder的imageView持有的Bitmap

为什么有这么多的adatper!

多个viewHolder,

步骤4.1:分析泄漏报告
在 Leak Suspects 标签页:
csharp
Problem Suspect 1
85.7 MB retained by 3 instances of com.example.OomRecyclerActivity
• Dominated by static field com.example.OomRecyclerActivity.leakedActivities
• Contains 20 instances of android.graphics.Bitmap
Problem Suspect 2
15.2 MB retained by 150 instances of android.view.View$ListenerInfo
• Associated with OomRecyclerActivity$LeakyAdapter$1
点击 Details >> 查看完整引用链
步骤 4.2:直方图分析(Histogram)
-
点击工具栏 Histogram 图标
-
过滤关键类:
bashcom.example.OomRecyclerActivity OomRecyclerActivity$LeakyAdapter android.graphics.Bitmap
-
查看对象信息:
Class Name Objects Shallow Heap Retained Heap com.example.OomRecyclerActivity 3 1.2 KB 85.7 MB OomRecyclerActivity$LeakyAdapter 3 1.5 KB 84.3 MB android.graphics.Bitmap 60 1.8 KB 80.5 MB
步骤 4.3 :定位泄漏链
-
右键
OomRecyclerActivity
→ Merge Shortest Paths to GC Roots → exclude weak referencesarduino■ GC Root: static leakedActivities ■ class com.example.OomRecyclerActivity @ 0x6d3a8f00 ■ java.util.ArrayList @ 0x6d3a8f20 ■ [0] com.example.OomRecyclerActivity @ 0x6d3a8f40 ■ android.support.v7.widget.RecyclerView @ 0x6d3a8f60 ■ OomRecyclerActivity$LeakyAdapter @ 0x6d3a8f80 ■ java.util.ArrayList @ 0x6d3a8fa0 (bitmaps) ■ [0] android.graphics.Bitmap @ 0x6d3a8fc0 (3.81MB) ...
-
分析 Adapter 泄漏:
swift■ OomRecyclerActivity$LeakyAdapter @ 0x6d3a8f80 ■ this$0: OomRecyclerActivity$LeakyAdapter @ 0x6d3a8f80 ■ context: com.example.OomRecyclerActivity @ 0x6d3a8f40 // 强引用!
步骤 4.4:Bitmap 分析
-
打开 Dominator Tree
-
按 Retained Size 降序排序
-
定位 Bitmap 对象:
yamlandroid.graphics.Bitmap @ 0x6d3a8fc0 • Retained Heap: 3.81 MB • Width: 1000 • Height: 1000 • mBuffer: byte[4,000,000] @ 0x6d3a9000
-
查看分配栈:
yaml■ Creation Site: OomRecyclerActivity$LeakyAdapter.loadLargeBitmaps() Line 58: Bitmap.createBitmap(1000, 1000, ARGB_8888)
4.5. 比较堆转储
-
捕获修复前后的堆转储
-
MAT → Tools → Compare Heap Dumps
-
查看差异:
diff
diff- com.example.OomRecyclerActivity : 3 instances (-85.7MB) + com.example.OomRecyclerActivity : 0 instances
4.6 OQL 内存查询
vbnet
// 查找大尺寸 Bitmap
SELECT * FROM android.graphics.Bitmap
WHERE (width * height * 4) > 4000000
// 查找被销毁的 Activity
SELECT * FROM java.lang.Object
WHERE (toString() LIKE "Activity@%")
AND (dominator().toString() LIKE "StaticRoot%")

5.总结
5. 1分析Activity泄漏
步骤:
- 打开MAT并加载堆转储文件
- 执行OQL查询查找Activity实例:
SELECT * FROM com.example.MemoryLeakActivity
- 如果发现Activity实例仍然存在(应该已经被销毁),右键选择"Path to GC Roots" → "exclude weak/soft references"
- 分析引用链
预期发现:
- 静态集合leakedActivities持有Activity引用
- Adapter通过context字段持有Activity引用
- 点击监听器的匿名内部类隐式持有Activity引用
5.2. 分析Adapter和ViewHolder泄漏
- 查找Adapter实例:
SELECT * FROM com.example.MemoryLeakActivity$LeakyAdapter
- 查看Adapter持有的对象:
- 检查context字段指向的对象
- 检查bitmaps集合的大小和内容
- 查找ViewHolder实例:
SELECT * FROM com.example.MemoryLeakActivity$LeakyViewHolder
- 检查ViewHolder数量是否合理(应该与屏幕可见数量相近)
预期发现:
- Adapter持有大量ViewHolder
- ViewHolder持有未回收的Bitmap
5.3. 分析Bitmap内存问题
步骤:
- 查看内存占用最大的对象:
- 点击"Histogram"视图
- 按retained size排序
- 查找Bitmap实例:
SELECT * FROM android.graphics.Bitmap
- 分析Bitmap的尺寸和数量:
SELECT width, height, COUNT(*) FROM android.graphics.Bitmap GROUP BY width, height
- 检查Bitmap的引用链:
- 右键Bitmap实例 → "Path to GC Roots"
预期发现:
- 大量1000x1000的Bitmap实例
- Bitmap被Adapter的bitmaps集合持有
6. 备注
项目源码的地址:github.com/pengcaihua1...
adb shell dumpsys meminfo <package_name>