如何使用 Android Profiler 在 Android 中查找不需要的引用

How to Find Unwanted References in Android Using Android Profiler

我正在努力更好地查找 Android 中的内存泄漏。

我发现了 Android 探查器并学习了如何执行堆转储以及如何确定内存中给定对象的实例是否过多。

我已经读到,找到 为什么 一个不需要的对象仍然存在的根源的方法之一是突出显示它并且 "look at what objects are still holding references to it and trace your way back to the original cause."

所以...在这个屏幕截图中你可以看到不希望出现的情况:我有三个 MainActivity...的实例,并且所有三个实例在 "depth" 列信号中都有一个数字他们真的是泄密者。

如果有问题的对象是我自己创造的 class,那么这个过程会更直接,但由于我们在这里处理的是一个实际的 Activity,当我突出显示这三个中的任何一个,有大量引用它的对象列表(列表远远超出屏幕截图)。

其中大部分肯定是 normal/benign 参考资料 -- 我应该如何判断哪些值得研究?

线索是什么?是“this$0”吗?还是保留列中的大量数字?与相关对象匹配的深度数?我现在只是猜测。

我当然不希望自己仔细考虑整个列表,"Nope... can't be that one... that's a normal part of Android Framework X,Y, and Z..."

I'm trying to get better at hunting down memory leaks in Android.

我会举几个例子,但首先,由于你问的问题数量与你的主题无关 我将首先解释您在 "Instance view";

中看到的内容
  • 深度:从任何 GC 根到所选实例的最短跳数。

    • 0 用于 "private final class" 或静态成员
    • 如果它是空白的,它将被回收 - 这对您无害
  • 浅尺寸:此实例在 Java 内存中的尺寸

  • Retained Size:此实例支配的内存大小(根据 dominator 树)。

Is it the this[=15=]?

this[=12=]是编译器生成的合成变量,意思是"one level out of my scope",它是非静态内部class.

的父对象

How am I supposed to tell which ones are worth investigating?

这取决于,如果 depth 是 0 并且它是您的代码 - 调查,也许这是一个很长的 运行ning 任务,结束条件不好。

在 Memory Profiler 中分析堆转储时,您可以过滤 Android Studio 认为可能表明 Activity 和应用中的 Fragment 实例存在内存泄漏的分析数据。

过滤器显示的数据类型包括:

  • Activity个已被销毁但仍在引用的实例。

  • 片段实例没有有效的 FragmentManager 但仍在被引用。

在某些情况下,例如以下情况,过滤器可能会产生 误报:

  • Fragment 已创建但尚未使用。

  • 正在缓存一个片段,但不是片段事务的一部分。

正在针对内存泄漏过滤堆转储。

分析记忆的技巧

在使用 Memory Profiler 时,您应该强调您的应用程序代码并尝试强制内存泄漏。

在您的应用程序中引发内存泄漏的一种方法是在检查堆之前让它 运行 一段时间。

泄漏可能会蔓延到堆中分配的顶部。

但是,泄漏越小,您需要 运行 应用程序才能看到它的时间越长。

您还可以通过以下方式之一触发内存泄漏:

  • 在不同的 activity 状态下,将设备从纵向旋转到横向并再次旋转多次。旋转设备通常会导致应用程序泄漏 Activity、上下文或视图对象,因为系统会重新创建 Activity,并且如果您的应用程序在其他地方持有对这些对象之一的引用,系统可以'垃圾收集它。
  • 在处于不同 activity 状态时在您的应用和另一个应用之间切换(导航至主屏幕,然后 return 至您的应用)。

增长图是一个很大的指标

如果您观察到一条趋势线只持续上升而很少下降,则可能是内存泄漏,这意味着某些内存无法释放。或者根本没有足够的内存来处理应用程序。当应用程序达到其内存限制并且 Android OS 无法为应用程序分配更多内存时,将抛出 OutOfMemoryError。

短时间内的动荡

湍流是不稳定的指标,这也适用于 Android 内存使用。当我们观察这种模式时,通常会在其短暂的生命周期中创建并丢弃大量昂贵的对象。

CPU 在执行垃圾收集时浪费了很多周期,而没有执行应用程序的实际工作。用户可能会遇到缓慢的 UI,在这种情况下我们绝对应该优化我们的内存使用。

如果我们谈论 Java 内存泄漏

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);

        new DownloadTask().start();
    }

    private class DownloadTask extends Thread {
        @Override
        public void run() {
           SystemClock.sleep(2000 * 10);
        }
    }
}

内部 classes 持有对其封闭 class 的隐式引用,它将自动生成一个构造函数并将 activity 作为对它的引用传递。

上面的代码其实就是

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);

        new DownloadTask(this).start();
    }

    private class DownloadTask extends Thread {

        Activity activity;

        public DownloadTask(Activity activity) {
            this.activity = activity;
        }

        @Override
        public void run() {
            SystemClock.sleep(2000 * 10);
        }
    }
}

正常情况下,用户打开activity等待20秒直到下载任务完成。

任务完成后,堆栈释放所有对象。

下次垃圾收集器工作时,他将从堆中释放对象。

并且当用户关闭 activity 时,main 方法将从堆栈中释放,ThreadActivity 也会从堆中回收,一切都按需工作,没有泄漏。

如果用户 closes/rotate 在 10 秒后 activity

任务仍在运行,这意味着 activity 的引用仍然存在,我们有 内存泄漏

注意:当下载 运行() 任务完成时,堆栈释放对象。所以当垃圾收集器下次工作时,对象将从堆中回收,因为上面没有引用对象。

related Youtube playlist

而且 https://square.github.io/leakcanary/fundamentals/ 很棒。