简介:
KOOM是快手团队开源的Android内存监控与分析工具,主要是解决OOM问题,图片,大对象, 其次能够自动检测Java内存泄漏、大对象和线程泄漏等问题
与传统工具相比,KOOM具有三大核心优势:
- 线上友好:采用轻量级JSON报告(KB级)替代传统HPROF文件(MB级)
- 全面监控:覆盖Activity泄漏、大对象(Bitmap/数组)和线程泄漏等多维度问题
- 高性能:基于Fork子进程的Copy-on-Write技术,避免应用卡顿
目前 KOOM 已经具备了 Java Heap/Native Heap/Thread 泄漏监控能力
KOOM为了解决什么问题,产生的背景
1).客户端OOM问题是比较难处理的。一般的崩溃问题,只要获取崩溃瞬时的数据,比如异常类型、堆栈等就都比较容易解决,而 OOM 往往是多个因素累加在一起形成的,并且和用户操作的过程有很大关系。没有成熟的线上内存监控体系,一般我们只能采用线下复现的方式,效率很低下,不能满足需求。
2).主流的LeakCanary可以针对Activity或者Fragment的来优化OOM问题,但是受限于性能原因,线上无法大规模使用,一搬都是用于线下定位问题使用。还有其他的方案比如腾讯Matrix的ResouceCanary,也是基于LeakCanary为基础做优化,没有完全解决监控过程中的性能问题。
1.如何运行官方的demo
官网地址: KOOM/README.zh-CN.md at master · KwaiAppTeam/KOOM · GitHub
1.1 studio中~ 编译的使用11 的版本
1.2.修改根gradle的顺序
scss
allprojects {
repositories {
mavenCentral()
google()
mavenLocal()
maven { url 'https://jitpack.io' }
maven{ url 'https://s01.oss.sonatype.org/service/local/repositories/releases/content/'}
}
}
1.3. 注释掉根gradle这个依赖, 因为整个依赖已经找不到了
arduino
// classpath 'com.novoda:bintray-release:0.9.2'
1.4 集成方案:
koom的集成 ! 是否会和leakcanary进行冲突!
公司项目集成了leakcanary,再集成KOOM闪退 · Issue #19 · Kwai...
arduino
implementation "com.kuaishou.koom:koom-java-leak:2.2.0"
implementation 'com.kuaishou.koom:koom-monitor-base:2.2.0'
2. KOOM产生的报告分析
KOOM生成的报告包含JSON和HPROF文件
2.1 存放的路径: json和hprof内存快照
bash
/storage/emulated/0/Android/data/[包名]/files/performance/oom/memory/hprof-aly/
├── 1.0.0_2025-05-20_19-50-36_779.hprof // 内存快照
└── 1.0.0_2025-05-20_20-22-29_257.json // 分析报告
在sdcard/android/
/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly/1.0.0_2025-05-20_19-50-36_779.hprof
/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly/1.0.0_2025-05-20_20-22-29_257.json
2.2 报告的含义:
2.2.1 报告结构解析
该json中包含:
- runningInfo: app 当前进程信息,包含线程数、fd 数据等关键信息
- gcPaths: 触发gc的对象的调用链
- leakObjects:泄漏对象
- classInfos:类信息
JSON报告核心字段解析
字段 | 说明 | 示例值 |
---|---|---|
leakObjects |
泄漏对象列表 | Bitmap、大数组等 |
gcPaths |
GC引用链 | 泄漏对象的完整引用路径 |
runningInfo |
运行时状态 | 内存使用、线程数等 |
classInfos |
类实例统计 | 各类型对象实例数量 |
先来看下,leakObjects:泄漏对象列表
css
**[ **{ "className": "android.graphics.Bitmap", "extDetail": "1920 x 1080", "objectId": "327801464", "size": "2073600" }, **{ "className": "int[]", "objectId": "1972002816", "size": "455869" }, **{ "className": "byte[]", "objectId": "1973350400", "size": "524301" }, **{ "className": "char[]", "objectId": "1974407184", "size": "1048589" } ]
从上面看,可知有bimap 和数组存在泄漏,但无更详细信息。
接下来看下gcPaths中一部分信息:泄漏对象的GC引用链
json
**{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Bitmap Size Over Threshold, 1920x1080",
"path": **[
**{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
**{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
**{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType": "STATIC_FIELD"
},
**{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
**{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
**{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList",
"referenceType": "INSTANCE_FIELD"
},
**{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
**{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
**{
"reference": "android.graphics.Bitmap",
"referenceType": "instance"
}
],
"signature": "38ba5ba71b7599737372f965417abcf2765dbb2a"
}
从gc 调用链看出,bitmap 被LeakMaker持有,LeakMaker 被BitmapLeakMaker持有,BitmapLeakMaker被LeakMaker 中静态leakMakerList持有,导致bitmap 一直无法被释放。
接下来看下runningInfo的部分信息:
json
{
"buildModel": "PCLM50",
"currentPage": "javaleak.JavaLeakTestActivity",
"deviceMemAvaliable": "3643.6367",
"deviceMemTotal": "7398.6797",
"dumpReason": "reason_thread_oom",
"fdCount": "138",
"filterInstanceTime": "1.837",
"findGCPathTime": "16.967",
"jvmMax": "384.0",
"jvmUsed": "6.4137344",
"manufacture": "OPPO",
"nowTime": "2022-08-17_15-29-50_432",
"pss": "125.66699mb",
"rss": "161.82812mb",
"sdkInt": "31",
"threadCount": "725"
}
2.2.2 KOOM的报告是json格式,并且大小在KB级别,样式如下所示:

HPROF Agent
2.2.3 HPROF文件的格式


具体的报告json数据:
json
{
"instanceSummary": [
{
"className": "libcore.util.NativeAllocationRegistry$CleanerThunk",
"instanceCount": "2727"
},
{
"className": "libcore.util.NativeAllocationRegistry",
"instanceCount": "117"
},
{
"className": "com.android.internal.policy.PhoneWindow",
"instanceCount": "4"
},
{
"className": "com.kwai.koom.demo.threadleak.ThreadLeakTestActivity",
"instanceCount": "1"
},
{
"className": "com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
"instanceCount": "1"
},
{
"className": "android.graphics.Bitmap",
"instanceCount": "1"
}
],
"gcPaths": [
{
"gcRoot": "System class",
"instanceCount": 1,
"leakReason": "Primitive Array Size Over Threshold, 455869",
"path": [
{
"declaredClass": "android.icu.impl.coll.CollationRoot",
"reference": "android.icu.impl.coll.CollationRoot.rootSingleton",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "android.icu.impl.coll.CollationTailoring",
"reference": "android.icu.impl.coll.CollationTailoring.trie",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "android.icu.impl.Trie2",
"reference": "android.icu.impl.Trie2_32.data32",
"referenceType": "INSTANCE_FIELD"
},
{
"reference": "int[]",
"referenceType": "array"
}
],
"signature": "72c7b40fff431b6442d8de34a1f395379db13b9"
},
{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Activity Leak",
"path": [
{
"declaredClass": "java.lang.Thread",
"reference": "android.os.HandlerThread.contextClassLoader",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
"reference": "com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity.uselessObjectList",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"reference": "com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
"referenceType": "instance"
}
],
"signature": "f55bda18522c608841f9c306d9d5d9954c68a76"
},
{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Activity Leak",
"path": [
{
"declaredClass": "java.lang.Thread",
"reference": "android.os.HandlerThread.contextClassLoader",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.performance.overhead.thread.monitor.ThreadMonitor",
"reference": "com.kwai.performance.overhead.thread.monitor.ThreadMonitor.INSTANCE",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "com.kwai.koom.base.Monitor",
"reference": "com.kwai.performance.overhead.thread.monitor.ThreadMonitor._monitorConfig",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "com.kwai.performance.overhead.thread.monitor.ThreadMonitorConfig",
"reference": "com.kwai.performance.overhead.thread.monitor.ThreadMonitorConfig.listener",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "com.kwai.koom.demo.threadleak.ThreadLeakTestActivity$initMonitor$listener$1",
"reference": "com.kwai.koom.demo.threadleak.ThreadLeakTestActivity$initMonitor$listener$1.this$0",
"referenceType": "INSTANCE_FIELD"
},
{
"reference": "com.kwai.koom.demo.threadleak.ThreadLeakTestActivity",
"referenceType": "instance"
}
],
"signature": "e346c020b938775bad484e4beb71c2b54d748efd"
},
{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Bitmap Size Over Threshold, 1920x1080",
"path": [
{
"declaredClass": "java.lang.Thread",
"reference": "android.os.HandlerThread.contextClassLoader",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"reference": "android.graphics.Bitmap",
"referenceType": "instance"
}
],
"signature": "18de15e2cba5f1be901eeb58b71b43e8134db"
},
{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Primitive Array Size Over Threshold, 524301",
"path": [
{
"declaredClass": "java.lang.Thread",
"reference": "android.os.HandlerThread.contextClassLoader",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker.uselessObjectList",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder",
"reference": "com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder.array",
"referenceType": "INSTANCE_FIELD"
},
{
"reference": "byte[]",
"referenceType": "array"
}
],
"signature": "a6922369a626d2c7ffa53a3fd4829edb80e877"
}
],
"leakObjects": [
{
"className": "android.graphics.Bitmap",
"extDetail": "1920 x 1080",
"objectId": "328880216",
"size": "2073600"
},
{
"className": "int[]",
"objectId": "1983541248",
"size": "455869"
},
{
"className": "byte[]",
"objectId": "1984491520",
"size": "524301"
},
{
"className": "char[]",
"objectId": "1985548304",
"size": "1048589"
}
],
"runningInfo": {
"buildModel": "M2010J19SC",
"currentPage": "javaleak.JavaLeakTestActivity",
"deviceMemAvaliable": "1408.6367",
"deviceMemTotal": "5706.3438",
"dumpReason": "reason_thread_oom",
"fdCount": "133",
"filterInstanceTime": "2.282",
"findGCPathTime": "20.256",
"jvmMax": "256.0",
"jvmUsed": "6.734436",
"manufacture": "Xiaomi",
"nowTime": "2025-05-20_20-40-07_630",
"pss": "121.25293mb",
"rss": "144.69922mb",
"sdkInt": "31",
"threadCount": "722",
"threadList": [
".kwai.koom.demo",
"Signal Catcher",
"perfetto_hprof_",
"ADB-JDWP Connec",
"Jit thread pool",
"HeapTaskDaemon",
"ReferenceQueueD",
"FinalizerDaemon",
"FinalizerWatchd",
"Binder:21439_1",
"Binder:21439_2",
"Binder:21439_3",
"21439-ScoutStat",
"Profile Saver",
"RenderThread",
"Binder:intercep",
"hwuiTask0",
"hwuiTask1",
"Binder:21439_3",
"LoopThread",
"Binder:21439_4",
"queued-work-loo",
"Thread-3",
"Thread-4",
"Thread-5",
"Thread-6",
"Thread-7",
"Thread-8",
"Thread-9",
"Thread-10",
"Thread-11",
"Thread-12",
"Thread-13"
],
"usageSeconds": "9",
"vss": "18131.719mb"
}
}
3.demo如何引起的内存问题?
3.1 demo的图片

demo,有多种泄漏同时,我们为了更好的验证,只放开一个,然后其他的注释,进行测试
内存泄漏制造源码
csharp
public static void makeLeak(Context context) {
leakMakerList.add(new ActivityLeakMaker());
leakMakerList.add(new BitmapLeakMaker());
leakMakerList.add(new ByteArrayLeakMaker());
leakMakerList.add(new FragmentLeakMaker());
leakMakerList.add(new StringLeakMaker());
for (LeakMaker leakMaker : leakMakerList) {
leakMaker.startLeak(context);
}
for (int i = 0; i < 700; i++) { // 线程太多
new Thread(() -> {
try {
Thread.sleep(200000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
进行分步骤分析!
3.2 Activity导致的内存泄露
直接运行,是没有反应的,因为不触发dump!
添加和线程的一起就会触发!
原因是: Koom的机制,是不会里面检测内存泄漏的! 是通过定时检测内存阈值dump分析的! 后面的文章会详细讲原理
源码:
scala
public class ActivityLeakMaker extends LeakMaker<Activity> {
@Override
public void startLeak(Context context) {
LeakedActivity.setUselessObjectList(uselessObjectList);
Intent intent = new Intent(context, LeakedActivity.class);
context.startActivity(intent);
}
public static class LeakedActivity extends AppCompatActivity {
static List<Activity> uselessObjectList;
public static void setUselessObjectList(List<Activity> activities) {
uselessObjectList = activities;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uselessObjectList.add(this);
this.finish();
}
}
}
Activity泄漏路径
json
{
"gcRoot": "Local variable in native code",
"leakReason": "Activity Leak",
"path": [
"Thread → HandlerThread.contextClassLoader",
"PathClassLoader.runtimeInternalObjects",
"ActivityLeakMaker$LeakedActivity.uselessObjectList(静态字段)",
"ArrayList.elementData",
"LeakedActivity实例"
]
}
问题 :静态字段uselessObjectList
持有Activity实例,导致无法回收
Thread → ClassLoader → 静态集合 → Activity实例
修复方案:用弱引用
3.3 Bitmap大图泄漏
泄漏特征:
- leakObjects中存在大尺寸Bitmap
- 报告显示"Bitmap Size Over Threshold"
泄漏对象:1920×1080分辨率Bitmap(约2MB)
没有压缩的图片, 直接用的原图
scala
public class BitmapLeakMaker extends LeakMaker<Bitmap> {
@Override
public void startLeak(Context context) {
Bitmap bitmap = Bitmap.createBitmap(1920, 1080, Bitmap.Config.ARGB_8888);
uselessObjectList.add(bitmap);
}
}
3.3.1 Bitmap泄漏路径
json
{
"gcRoot": "Local variable in native code",
"leakReason": "Bitmap Size Over Threshold, 1920x1080",
"path": [
"Thread → ClassLoader",
"LeakMaker.leakMakerList(静态字段)",
"ArrayList → BitmapLeakMaker.uselessObjectList",
"ArrayList → Bitmap实例"
]
}
泄漏路径分析:
Thread → ClassLoader → LeakMaker.leakMakerList(静态字段) →
ArrayList → BitmapLeakMaker.uselessObjectList → Bitmap实例
根本原因:静态集合leakMakerList持有Bitmap导致无法回收
问题:静态集合通过多层引用持有大Bitmap
典型路径:
静态集合 → ArrayList → Bitmap实例
修复方案:图片进行缩小和压缩
3.4 大对象
比如字节数组, 大数组泄漏
泄漏特征:
- leakObjects中存在大size的byte[]、int[]等
- 报告显示"Primitive Array Size Over Threshold"
典型路径:
json
{
"leakReason": "Primitive Array Size Over Threshold, 524301",
"path": [
"Thread → ClassLoader",
"LeakMaker.leakMakerList(静态字段)",
"ArrayList → ByteArrayLeakMaker.uselessObjectList",
"ArrayList → ByteArrayHolder.array",
"大byte数组"
]
}
静态集合 → 包装类 → 大数组
源码:
csharp
public static class ByteArrayHolder {
private byte[] array;
public ByteArrayHolder() {
array = new byte[512 * 1024];
}
}
修复方案:处理完后及时置空,使用对象池复用大数组
3.4.2 不知道这个是啥? 大的字节数组!
javascript
void startLeak(Context context) {
String largeStr = new String(new byte[512 * 1024]);
uselessObjectList.add(largeStr);
}
3.4 很多线程
线程会立马dump 内存! 泄漏特征:
-
runningInfo中threadCount异常高(如700+)
-
dumpReason为"reason_thread_oom"
线程总数:722个(含700+无名线程)
直接表现:触发reason_thread_oom(线程OOM)
csharp
public abstract class LeakMaker<T> {
List<T> uselessObjectList = new ArrayList<>();
abstract void startLeak(Context context);
private static List<LeakMaker> leakMakerList = new ArrayList<>();
public static void makeLeak(Context context) {
leakMakerList.add(new ActivityLeakMaker());
leakMakerList.add(new BitmapLeakMaker());
leakMakerList.add(new ByteArrayLeakMaker());
leakMakerList.add(new FragmentLeakMaker());
leakMakerList.add(new StringLeakMaker());
for (LeakMaker leakMaker : leakMakerList) {
leakMaker.startLeak(context);
}
for (int i = 0; i < 700; i++) { // 线程太多
new Thread(() -> {
try {
Thread.sleep(200000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
修复方案:用线程池
4.Profiler 进行分析Koom产生的hprof文件
新版本profile是如何进行打开已经有的hprof文件!
尝试路径:
File > Open:直接选择 .hprof 文件,Android Studio 可能会自动识别并用 Profiler 或内置工具打开。
分析完发现: 并不是图片,而是int【】数组和 byte类型的数组【】
TAG: OOMMonitor_SystemInfo
图片也是不能直接用的!
其他的都不行! 需要搭配线程进行抓取
5.MAT 进行分析Koom产生的hprof文件
-
Android Studio分析:
- File → Open → 选择.hprof文件
- 查看Dominator Tree和Histogram
-
关键分析点:
- 检查大对象(Retained Size排序)
- 查看重复创建的相同类型对象
- 分析GC Roots引用链
-
常见MAT使用场景:
- 查找Activity泄漏:搜索Activity实例数量
- 分析Bitmap:按size排序查看大图
- 检查集合类:查看ArrayList等集合内容
5.1 裁剪的hprof直接用studio打开 后续对案例详细介绍
5. KOOM pK Leakcanary
维度 | LeakCanary | KOOM |
---|---|---|
设计目标 | 线下开发调试 | 线上大规模监控 210 |
触发机制 | 监听onDestroy 后主动触发GC检测 |
内存阈值轮询(如连续3次超80%)26 |
Dump技术 | 主进程直接dump(冻结应用数秒) | Fork子进程 + Copy-on-Write(无感)210 |
检测范围 | Activity/Fragment泄漏 | Activity/Fragment/Bitmap/大数组/线程18 |
性能影响 | 高(主动GC、主进程冻结) | 低(子线程监控,无主动GC)10 |
报告形式 | 完整堆栈详情(MB级) | 轻量JSON报告(KB级)10 |
线上适用性 | 不推荐(性能损耗大) | 支持全量部署510 |
推荐组合方案:开发阶段用LeakCanary快速定位问题,线上用KOOM全量监控
6. 总结

检查项 | 达标标准 | 检测方法 |
---|---|---|
Activity泄漏 | 不超过2个相同Activity实例 | KOOM报告/MAT |
Bitmap内存 | 不超过屏幕尺寸2倍 | KOOM大对象检测 |
线程数量 | 不超过50个 | runningInfo.threadCount |
大数组(>100KB) | 不超过10个 | KOOM Primitive Array检测 |
KOOM内存分析报告深度解析
这份报告显示了应用存在多种严重的内存问题,我将从关键问题点、泄漏路径和修复建议三个方面进行分析:
bash
{
"classInfos": [
{
"className": "libcore.util.NativeAllocationRegistry$CleanerThunk",
"instanceCount": "2443"
},
{
"className": "libcore.util.NativeAllocationRegistry",
"instanceCount": "115"
},
{
"className": "com.android.internal.policy.PhoneWindow",
"instanceCount": "2"
},
{
"className": "android.graphics.Bitmap",
"instanceCount": "1"
}
],
"gcPaths": [
{
"gcRoot": "System class",
"instanceCount": 1,
"leakReason": "Primitive Array Size Over Threshold, 455869",
"path": [
{
"declaredClass": "android.icu.impl.coll.CollationRoot",
"reference": "android.icu.impl.coll.CollationRoot.rootSingleton",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "android.icu.impl.coll.CollationTailoring",
"reference": "android.icu.impl.coll.CollationTailoring.trie",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "android.icu.impl.Trie2",
"reference": "android.icu.impl.Trie2_32.data32",
"referenceType": "INSTANCE_FIELD"
},
{
"reference": "int[]",
"referenceType": "array"
}
],
"signature": "72c7b40fff431b6442d8de34a1f395379db13b9"
},
{
"gcRoot": "Local variable in native code",
"instanceCount": 1,
"leakReason": "Bitmap Size Over Threshold, 1920x1080",
"path": [
{
"declaredClass": "java.lang.Thread",
"reference": "android.os.HandlerThread.contextClassLoader",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.ClassLoader",
"reference": "dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType": "STATIC_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"declaredClass": "com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference": "com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.util.ArrayList",
"reference": "java.util.ArrayList.elementData",
"referenceType": "INSTANCE_FIELD"
},
{
"declaredClass": "java.lang.Object[]",
"reference": "java.lang.Object[]",
"referenceType": "ARRAY_ENTRY"
},
{
"reference": "android.graphics.Bitmap",
"referenceType": "instance"
}
],
"signature": "18de15e2cba5f1be901eeb58b71b43e8134db"
}
],
"leakObjects": [],
"runningInfo": {
"buildModel": "M2010J19SC",
"currentPage": "javaleak.JavaLeakTestActivity",
"deviceMemAvaliable": "2482.1445",
"deviceMemTotal": "5706.3438",
"dumpReason": "reason_thread_oom",
"fdCount": "135",
"filterInstanceTime": "2.262",
"findGCPathTime": "14.156",
"jvmMax": "256.0",
"jvmUsed": "3.7849655",
"manufacture": "Xiaomi",
"nowTime": "2025-05-22_11-05-49_068",
"pss": "120.88574mb",
"rss": "141.15234mb",
"sdkInt": "31",
"threadCount": "722",
"threadList": [
".kwai.koom.demo",
"Signal Catcher",
"perfetto_hprof_",
"ADB-JDWP Connec",
"Jit thread pool",
"HeapTaskDaemon",
"ReferenceQueueD",
"FinalizerDaemon",
"FinalizerWatchd",
"Binder:13231_1",
"Binder:13231_2",
"Binder:13231_3",
"13231-ScoutStat",
"Profile Saver",
"RenderThread",
"Binder:intercep",
"hwuiTask0",
"hwuiTask1"
],
"usageSeconds": "9",
"vss": "18000.016mb"
}
}
6.1 类实例统计(classInfos)
- Native资源问题:
-
NativeAllocationRegistry$CleanerThunk
:2727个实例(Native内存清理相关) -
NativeAllocationRegistry
:117个实例
- 内存泄漏:
-
PhoneWindow
:4个实例(通常Activity销毁后应被回收) -
ThreadLeakTestActivity
:1个实例(应检查是否泄漏) -
LeakedActivity
:1个实例(明确泄漏) -
Bitmap
:1个实例(1920x1080大图)
6.2 内存对象(leakObjects)
-
Bitmap
:2,073,600字节(约2MB) -
int[]
:455,869字节(约445KB) -
byte[]
:524,301字节(约512KB) -
char[]
:1,048,589字节(约1MB)
6.3 运行状态(runningInfo)
-
线程爆炸:722个线程(严重异常)
-
内存使用:
-
PSS:121.25MB
-
RSS:144.69MB
- 触发原因 :
reason_thread_oom
(线程OOM)
7.遗留问题:
7.1 如何修改源码,适配Android 17
7.2 有些大数组没有,根本没有调用也显示了,还有native,为什么会自动触发了
A:系统内部使用的缓存(如ICU4J的排序数据)可能被KOOM识别为大对象:
json
{
"gcRoot": "System class",
"leakReason": "Primitive Array Size Over Threshold, 455869",
"path": [
"CollationRoot.rootSingleton(静态字段)",
"CollationTailoring.trie",
"Trie2_32.data32",
"int[]"
]
}
这类"泄漏"通常是系统级缓存,实际无需处理。