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

Boo*_*unz 6 android memory-leaks android-profiler

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

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

我已经读过,找到一个不需要的对象为什么仍然存在的根源的方法之一是突出显示它并“查看哪些对象仍然持有对它的引用并追溯到原始原因。”

所以...在此屏幕截图中,您可以看到不良情况:我有三个 MainActivity 实例...并且所有三个实例在“深度”列中都有一个数字,表明它们确实是泄漏。

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

当然,其中大部分都是正常/良性参考——我该如何判断哪些值得研究?

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

当然,我不希望自己仔细阅读整个列表,“不......不可能是那个......这是Android Framework X,Y和Z的正常部分......”

在此处输入图片说明

Jim*_*est 6

\n

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

\n
\n\n

我将举几个例子,但首先,由于您提出的问题数量与您的主题无关\n我将首先解释您在“实例视图”中看到的内容;

\n\n
    \n
  • 深度:从任何 GC 根到所选实例的最短跳数。

    \n\n
      \n
    • 0 表示“私有最终类”或静态成员
    • \n
    • 如果它是空白的,它将被回收 - 这对你来说是无害的
    • \n
  • \n
  • Shallow Size:Java 内存中该实例的大小

  • \n
  • 保留大小:此实例支配的内存大小(根据支配树)。

  • \n
\n\n
\n

是这个$0吗?

\n
\n\n

this$0是编译器生成的合成变量,意味着“超出我的范围”,它是非静态内部类的父对象。

\n\n
\n

我该如何判断哪些值得调查?

\n
\n\n

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

\n\n

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

\n\n

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

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

  • \n
  • 没有有效 FragmentManager 但仍被引用的 Fragment 实例。

  • \n
\n\n

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

\n\n
    \n
  • Fragment 已创建但尚未使用。

  • \n
  • Fragment 正在被缓存,但不作为 FragmentTransaction 的一部分。

  • \n
\n\n

筛选堆转储以查找内存泄漏。\n

\n\n

分析记忆的技巧

\n\n

使用内存分析器时,您应该强调您的应用程序代码并尝试强制内存泄漏。

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
    \n
  • 在不同的活动状态下,将设备从纵向旋转到横向,然后再返回多次。旋转设备通常会导致应用程序泄漏 Activity、Context 或 View 对象,因为系统会重新创建 Activity,并且如果您的应用程序在其他地方持有对这些对象之一的引用,则系统无法对其进行垃圾收集。
  • \n
  • 在不同的活动状态下在您的应用程序和另一个应用程序之间切换(导航到主屏幕,然后返回到您的应用程序)。
  • \n
\n\n

不断增长的图表是一个重要指标

\n\n

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

\n\n

短时间内出现湍流

\n\n

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

\n\n

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

\n\n

如果我们谈论 Java 内存泄漏\n在此输入图像描述

\n\n
public class ThreadActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_async_task);\n\n        new DownloadTask().start();\n    }\n\n    private class DownloadTask extends Thread {\n        @Override\n        public void run() {\n           SystemClock.sleep(2000 * 10);\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

上面的代码实际上是

\n\n
public class ThreadActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_async_task);\n\n        new DownloadTask(this).start();\n    }\n\n    private class DownloadTask extends Thread {\n\n        Activity activity;\n\n        public DownloadTask(Activity activity) {\n            this.activity = activity;\n        }\n\n        @Override\n        public void run() {\n            SystemClock.sleep(2000 * 10);\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

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

\n\n

当用户关闭 Activity 时,main 方法将从堆栈中释放,并且 ThreadActivity 也会从堆中回收,一切都按需要进行,不会泄漏。

\n\n

如果用户在 10 秒后关闭/旋转 Activity。

\n\n

该任务仍在工作,这意味着该活动的引用仍然存在,并且我们有内存泄漏

\n\n

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

\n\n

相关 YouTube 播放列表

\n\n

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

\n