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...

相关推荐
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte11 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc