【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 源码剖析

相关推荐
神仙别闹1 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭1 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫1 小时前
泛型(2)
java
超爱吃士力架2 小时前
邀请逻辑
java·linux·后端
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石2 小时前
12/21java基础
java
拭心2 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
李小白662 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp2 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶3 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb