【Android KOOM】KOOM Java层泄漏使用全解析

文章目录

前言

要研究内存泄漏框架,KOOM是个不可忽略的优秀开源框架,由快手开源。这将是一个系列的文章。这篇文章作为开篇,来看看KOOM框架的demo是怎么制造Java层面的leak的。

下面通过官方给出的Demo代码作为入口,来分析和学习这个开源框架。

初始化OOMMonitor

java 复制代码
OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());
        OOMMonitor.INSTANCE.startLoop(true, false,5_000L);

  LeakMaker.makeLeak(this);

make leak

在适当的地方初始化OOMMonitor,然后人为制造泄漏。

下面看看内存泄漏的写法:

java 复制代码
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();
    }
  }
}

新建了一个容器,把各种leak maker放进去,然后startLeak,接着开启大量的子线程。

需要注意的是startLeak写成了抽象方法,看下实现类:

java 复制代码
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();
    }
  }

}

会跳转到LeakedActivity,其中LeakedActivity里面会持有一个静态的成员变量uselessObjectList,在LeakedActivity创建好的时候把它传进去,这样一来,静态变量持有activity,便会在activity被销毁的时候发生内存泄漏,LeakedActivity被静态变量持有,无法被释放。

再看看另外一个leak maker:

java 复制代码
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);
  }
}

新建一张bitmap,然后添加到容器。

java 复制代码
public class ByteArrayLeakMaker extends LeakMaker<ByteArrayLeakMaker.ByteArrayHolder> {

  @Override
  public void startLeak(Context context) {
    uselessObjectList.add(new ByteArrayHolder());
  }

  public static class ByteArrayHolder {
    private byte[] array;

    public ByteArrayHolder() {
      array = new byte[512 * 1024];
    }
  }

}

这次是byte数组。

java 复制代码
public class StringLeakMaker extends LeakMaker<String> {

  @Override
  void startLeak(Context context) {
    String largeStr = new String(new byte[512 * 1024]);
    uselessObjectList.add(largeStr);
  }
}

这次是String,其它的也都大同小异。都是新建对象,然后添加抽象父类的容器里面,目的就是添加不同类的大对象到堆内存里面。便于后续测试框架检测leak的功能。

还有个需要注意的地方,这个地方很重要。LeakMaker抽象类里面的leakMakerList,这是个静态变量,把各个子类对象添加到leakMakerList,由于它是静态的,所以可以作为GCRoot。又因为它持有了这些子类LeakMaker对象,所以会导致这些子类LeakMaker对象无法被释放,造成内存泄漏。

各个leakMaker执行startLeak之后,开启大量的子线程,然后让子线程进行睡眠。

日志查看&堆快照输出

接着可以看到一些框架打印的log:

bash 复制代码
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_SystemInfo: ----OOM Monitor Memory----
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_SystemInfo: [java] max:402653184 used ratio:1%
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_SystemInfo: [proc] VmSize:2269420kB VmRss:94936kB Threads:718
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_SystemInfo: [meminfo] MemTotal:1548704kB MemFree:308960kB MemAvailable:973536kB
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_SystemInfo: avaliable ratio:62% CmaTotal:0kB ION_heap:0kB
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_ThreadOOMTracker: [meet condition] overThresholdCount:1, threadCount: 718
2024-03-08 16:46:52.102 5647-5687/com.kwai.koom.demo I/OOMMonitor_ThreadOOMTracker: over threshold dumpThreadIfNeed
2024-03-08 16:46:52.425 5647-5652/com.kwai.koom.demo I/zygote: Do partial code cache collection, code=123KB, data=57KB
2024-03-08 16:46:52.428 5647-5652/com.kwai.koom.demo I/zygote: After code cache collection, code=123KB, data=57KB
2024-03-08 16:46:52.428 5647-5652/com.kwai.koom.demo I/zygote: Increasing code cache capacity to 512KB
2024-03-08 16:46:52.524 5647-5687/com.kwai.koom.demo I/OOMMonitor_ThreadOOMTracker: threadNames = [.kwai.koom.demo, Jit thread pool, Signal Catcher, JDWP, ReferenceQueueD, FinalizerDaemon, FinalizerWatchd, HeapTaskDaemon, Binder:5647_1, Binder:5647_2, Profile Saver, RenderThread, RenderThread, hwuiTask1, hwuiTask2, Binder:5647_3, queued-work-loo, LoopThread, Thread-2, Thread-3, Thread-4, Thread-5, Thread-6, Thread-7, Thread-8, Thread-9, Thread-10, Thread-11, Thread-12, Thread-13, Thread-14, Thread-15, Thread-16, Thread-17, Thread-18, Thread-19, Thread-20, Thread-21, Thread-22, Thread-23, Thread-24, Thread-25, Thread-26, Thread-27, Thread-28, Thread-29, Thread-30, Thread-31, Thread-32, Thread-33, Thread-34, Thread-35, Thread-36, Thread-37, Thread-38, Thread-39, Thread-40, Thread-41, Thread-42, Thread-43, Thread-44, Thread-45, Thread-46, Thread-47, Thread-48, Thread-49, Thread-50, Thread-51, Thread-52, Thread-53, Thread-54, Thread-55, Thread-56, Thread-57, Thread-58, Thread-59, Thread-60, Thread-61, Thread-62, Thread-63, Thread-64, Thread-65, Thread-66, Thread-67, Thread-68, Thread-69, Thread-70, Thread-71, Thread-72, Thread-73, Thread-74, Thread-75, Thread-76, Thread-77, Thread-78, Thread-79, Thread-80, Thread-81, Thread-82, Thread-83, Thread-84, Thread-85, Thread-86, Thread-87, Thread-88, Thread-89, Thread-90, Thread-91, Thread-92, Thread-93, Thread-94, Thread-95, Thread-96, Thread-97, Thread-98, Thread-99, Thread-100, Thread-101, Thread-102, Thread-103, Thread-104, Thread-105, Thread-106, Thread-107, Thread-108, Thread-109, Thread-110, Thread-111, Thread-112, Thread-113, Thread-114, Thread-115, Thread-116, Thread-117, Thread-118, Thread-119, Thread-120, Thread-121, Thread-122, Thread-123, Thread-124, Thread-125, Thread-126, Thread-127, Thread-128, Thread-129, Thread-130, Thread-131, Thread-132, Thread-133, Thread-134, Thread-135, Thread-136, Thread-137, Thread-138, Thread-139, Thread-140, Thread-141, Thread-142, Thread-143, Thread-144, Thread-145, Thread-146, Thread-147, Thread-148, Thread-149, Thread-150, Thread-151, Thread-152, Thread-153, Thread-154, Thread-155, Thread-156, Thread-157, Thread-158, Thread-159, Thread-160, Thread-161, Thread-162, Thread-163, Thread-164, Thread-165, Thread-166, Thread-167, Thread-168, Thread-169, Thread-170, Thread-171, Thread-172, Thread-173, Thread-174, Thread-175, Thread-176, Thread-177, Thread-178, Thread-179, Thread-180, Thread-181, Thread-182, Thread-183, Thread-184, Thread-185, Thread-186, Thread-187, Thread-188, Thread-189, Thread-190, Thread-191, Thread-192, Thread-193, Thread-194, Thread-195, Thread-196, Thread-197, Thread-198, Thread-199, Thread-200, Thread-201, Thread-202, Thread-203, Thread-204, Thread-205, Thread-206, Thread-207, Thread-208, Thread-209, Thread-210, Thread-211, Thread-212, Thread-213, Thread-214, Thread-215, Thread-216, Thread-217, Thread-218, Thread-219, Thread-220, Thread-221, Thread-222, Thread-223, Thread-224, Thread-225, Thread-226, Thread-227, Thread-228, Thread-229, Thread-230, Thread-231, Thread-232, Thread-233, Thread-234, Thread-235, Thread-236, Thread-237, Thread-238, Thread-239, Thread-240, Thread-241, Thread-242, Thread-243, Thread-244, Thread-245, Thread-246, Thread-247, Thread-248, Thread-249, Thread-250, Thread-251, Thread-252, Thread-253, Thread-254, Thread-255, Thread-256, Thread-257, Thread-258, Thread-259, Thread-260, Thread-261, Thread-262, Thread-263, Thread-264, Thread-265, Thread-266, Thread-267, Thread-268, Thread-269, Thread-270, Thread-271, Thread-272, Thread-273, Thread-274, Thread-275, Thread-276, Thread-277, Thread-278, Thread-279, Thread-280, Thread-281, Thread-282, Thread-283, Thread-284, Thread-285, Thread-286, Thread-287, Thread-288, Thread-289, Thread-290, Thread-291, Thread-292, Thread-293, Thread-294, Thread-295, Thread-296, Thread-297, Thread-298, Thread-299, Thread-300, Thread-301, Thread-302, Thread-303, Thread-304, Thread-305, Thread-306, Thread-307, Thread-308, Thread-309, Thread-310, Thread-311, Thread-312, Thread-313, Thread-314, Thread-315, Thread-316, Thread-317, Thread-318, Thread-319, Thread-320, Thread-321, Thread-322, Thread-323, Thr
2024-03-08 16:46:52.532 5647-5687/com.kwai.koom.demo I/OOMMonitor: OOMPreferenceManager.getFirstAnalysisTime():1709884924454
2024-03-08 16:46:52.532 5647-5687/com.kwai.koom.demo I/OOMMonitor: OOMPreferenceManager.getAnalysisTimes:3
2024-03-08 16:46:52.533 5647-6389/com.kwai.koom.demo I/OOMMonitor: mTrackReasons:[reason_thread_oom]
2024-03-08 16:46:52.533 5647-6389/com.kwai.koom.demo I/OOMMonitor: dumpAndAnalysis
2024-03-08 16:46:52.535 5647-6390/com.kwai.koom.demo I/OOMMonitor: processHprofFile
2024-03-08 16:46:52.536 5647-6390/com.kwai.koom.demo I/OOMMonitor: delete other version files thread
2024-03-08 16:46:52.538 5647-6390/com.kwai.koom.demo I/OOMMonitor: delete old files
2024-03-08 16:46:52.542 5647-6390/com.kwai.koom.demo I/OOMMonitor: create json file and then start service
2024-03-08 16:46:52.545 5647-6390/com.kwai.koom.demo I/OOMMonitor_HeapAnalysisService: startAnalysisService
2024-03-08 16:46:52.549 5647-6390/com.kwai.koom.demo E/memtrack: Couldn't load memtrack module
2024-03-08 16:46:52.549 5647-6390/com.kwai.koom.demo W/android.os.Debug: failed to get memory consumption info: -1
2024-03-08 16:46:52.550 5647-6389/com.kwai.koom.demo I/OOMMonitor: hprof analysis dir:/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly
2024-03-08 16:46:52.550 5647-6389/com.kwai.koom.demo I/OOMMonitor_ForkJvmHeapDumper: dump /storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly/1.0.0_2024-03-08_16-46-52_535.hprof
2024-03-08 16:46:52.589 5647-6389/com.kwai.koom.demo V/kwai_dlfcn: android_api_ = 26
2024-03-08 16:46:52.653 5647-6389/com.kwai.koom.demo I/OOMMonitor_ForkJvmHeapDumper: before suspend and fork.
2024-03-08 16:46:52.766 5647-6390/com.kwai.koom.demo I/OOMMonitor_HeapAnalysisService: startAnalysisService get Pss:28731
2024-03-08 16:46:52.790 5647-6390/com.kwai.koom.demo I/OOMMonitor: delete other version files fd
2024-03-08 16:46:53.399 5647-6389/com.kwai.koom.demo I/OOMMonitor_ForkJvmHeapDumper: dump true, notify from pid 6391
2024-03-08 16:46:53.399 5647-6389/com.kwai.koom.demo I/OOMMonitor: end hprof dump
2024-03-08 16:46:54.400 5647-6389/com.kwai.koom.demo I/OOMMonitor: start hprof analysis
2024-03-08 16:46:54.401 5647-6389/com.kwai.koom.demo I/OOMMonitor_HeapAnalysisService: startAnalysisService
2024-03-08 16:46:54.401 5647-6389/com.kwai.koom.demo E/memtrack: Couldn't load memtrack module
2024-03-08 16:46:54.402 5647-6389/com.kwai.koom.demo W/android.os.Debug: failed to get memory consumption info: -1
2024-03-08 16:46:54.516 5647-6389/com.kwai.koom.demo I/OOMMonitor_HeapAnalysisService: startAnalysisService get Pss:39764
2024-03-08 16:46:55.207 5647-5660/com.kwai.koom.demo I/OOMMonitor: heap analysis success, do upload
2024-03-08 16:46:55.207 5647-5660/com.kwai.koom.demo I/OOMMonitor: {"classInfos":[{"className":"libcore.util.NativeAllocationRegistry$CleanerThunk","instanceCount":"969"},{"className":"androidx.lifecycle.ReportFragment","instanceCount":"1"},{"className":"com.android.internal.policy.PhoneWindow","instanceCount":"3"},{"className":"com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity","instanceCount":"1"},{"className":"libcore.util.NativeAllocationRegistry","instanceCount":"114"},{"className":"android.graphics.Bitmap","instanceCount":"1"}],"gcPaths":[{"gcRoot":"System class","instanceCount":1,"leakReason":"Activity Leak","path":[{"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":"39a5e85640a0de9a3a4d9caf88798ce8321a30"},{"gcRoot":"System class","instanceCount":1,"leakReason":"Bitmap Size Over Threshold, 1920x1080","path":[{"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":"a2f199eabc1292bcb4f813615818e5383a4988c8"},{"gcRoot":"System class","instanceCount":1,"leakReason":"Primitive Array Size Over Threshold, 524301","path":[{"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":"6d4e5ac7bf79b44c133b56b02a3b4ec1dd4aab"}],"leakObjects":[{"className":"android.graphics.Bitmap","extDetail":"1920 x 1080","objectId":"318378712","size":"2073600"},{"className":"char[]","objectId":"2394423312","size":"1048589"},{"className":"byte[]","objectId":"2397057024","size":"524301"}],"runningInfo":{"buildModel":"Android SDK built for x86","currentPage":"javaleak.JavaLeakTestActivity","deviceMemAvaliable":"950.71875","deviceMemTotal":"1512.4062","dumpReason":"reanalysis","fdCount":"56","filterInstanceTime":"0.424","findGCPathTime":"1.188","jvmMax":"384.0","jvmUsed":"4.2463074","manufacture":"Google","nowTime":"2024-03-08_16-46-52_774","pss":"28.057617mb","rss":"92.71094mb","sdkInt":
2024-03-08 16:46:55.208 5647-5660/com.kwai.koom.demo E/OOMMonitor: todo, upload report 1.0.0_2024-03-08_16-34-51_816.json if necessary
2024-03-08 16:46:55.208 5647-5660/com.kwai.koom.demo E/OOMMonitor: todo, upload hprof 1.0.0_2024-03-08_16-34-51_816.hprof if necessary
2024-03-08 16:50:07.178 5647-5710/com.kwai.koom.demo W/zygote: No such thread id for suspend: 86

从日志可以看出来,进行一轮heap分析之后,会在日志给出整体的数据。还会保存一份json格式和hprof格式的heap快照,开发者可以选择上传到自己的服务器上。

仔细观察还能发现一个细节,OOMMonitor_ForkJvmHeapDumper: before suspend and fork,也就是说,KOOM会挂起并fork当前进程。这样做的原因,大概是为了不要在GC STW的时候卡住APP。这也是和leakcanary框架不一样的地方。

本次代码导出的hprof文件已经通过hprof-conv命令进行转换,可以直接用MAT内存分析工具打开,文件已经上传。大家可以在文章头下载研究和学习。

至于更加深入的内容,这段时间会尽快在后续的文章会进一步分析和总结,同样在研究和学习性能相关的内容的朋友可以持续关注。

源码分析篇

这一篇会剖析KOOM的源码设计逻辑,已更新到下面链接:

【Android 内存优化】KOOM Java Leak 源码剖析

相关推荐
A-Jie-Y16 分钟前
JAVA框架-SpringBoot环境搭建指南
java·spring boot
深兰科技24 分钟前
深兰科技与淡水河谷合作推进:矿区示范加速落地
java·人工智能·python·c#·scala·symfony·深兰科技
码界奇点37 分钟前
基于Spring Boot的前后端分离商城系统设计与实现
java·spring boot·后端·java-ee·毕业设计·源代码管理
一叶飘零_sweeeet39 分钟前
深度剖析:Java 并发三大量难题 —— 死锁、活锁、饥饿全解
java·死锁·活锁·饥饿
IT乐手44 分钟前
java 对比分析对象是否有变化
android·java
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【18】Hook 接口和四大抽象类
java·人工智能·spring
Hachi被抢先注册了1 小时前
Docker学习记录
java·云原生·eureka
做时间的朋友。1 小时前
MySQL 8.0 窗口函数
android·数据库·mysql
举儿1 小时前
通过TRAE工具实现贪吃蛇游戏的全过程
android
守月满空山雪照窗1 小时前
深入理解 MTK FPSGO:Android 游戏帧率治理框架的架构与实现
android·游戏·架构