3.Android 内存优化 Koom分析官方案例实战

简介:

KOOM是快手团队开源的Android内存监控与分析工具,主要是解决OOM问题,图片,大对象, 其次能够自动检测Java内存泄漏、大对象和线程泄漏等问题

与传统工具相比,KOOM具有三大核心优势:

  1. 线上友好:采用轻量级JSON报告(KB级)替代传统HPROF文件(MB级)
  2. 全面监控:覆盖Activity泄漏、大对象(Bitmap/数组)和线程泄漏等多维度问题
  3. 高性能:基于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 报告的含义:

github.com/KwaiAppTeam...

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文件

  1. Android Studio分析

    • File → Open → 选择.hprof文件
    • 查看Dominator Tree和Histogram
  2. 关键分析点

    • 检查大对象(Retained Size排序)
    • 查看重复创建的相同类型对象
    • 分析GC Roots引用链
  3. 常见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[]"

   ]

}

这类"泄漏"通常是系统级缓存,实际无需处理。

项目的地址:github.com/pengcaihua1...

相关推荐
默默地离开4 分钟前
CSS定位全解析:从static到sticky的5种position属性详解(第五回)
前端·css
JosieBook7 分钟前
【web应用】前后端分离项目基本框架组成:Vue + Spring Boot 最佳实践指南
前端·vue.js·spring boot
用户214118326360212 分钟前
dify案例分享-告别手工录入!Dify 工作流一键生成发票申请预览,对接开票系统超简单
前端
子龙_13 分钟前
JS解析wav音频数据并使用wasm加速
前端·javascript·音视频开发
爱吃大橘17 分钟前
到底用 `Promise.reject` 还是 `throw new Error`
前端·javascript·面试
前端进阶者20 分钟前
浏览器绿屏仅关闭关video硬件加速
前端
小蘑菇201820 分钟前
mac前端环境安装
前端
aningxiaoxixi28 分钟前
android audio 之 Engine
android·前端·javascript
枣仁_30 分钟前
关于 `lodash.camelCase` 与 `type-fest` 差异的深度分析
前端
码农小菲41 分钟前
告别虚拟 DOM?Vue3.6 Vapor 模式的性能革命与实践
前端·javascript·vue.js