English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Практика Android Studio + MAT для борьбы с утечками памяти

Для утечки памяти, в Android, если не обращать внимания, это все же легко出现, особенно в Activity, особенно часто встречается,下面就来说说我如何查找内存泄漏的方法。

Сначала что такое утечка памяти?

Утечка памяти - это объекты, которые уже не используются и все еще существуют в памяти, и механизм переработки мусора не может их回收, что приводит к тому, что они постоянно занимают память, что в конечном итоге приводит к снижению производительности программы.
В Android виртуальной машине используется алгоритм поиска корневых узлов для перечисления корневых узлов и определения, является ли узел мусором. Виртуальная машина начинает с GC Roots и пройдёт по ним. Если узел не может найти путь к GC Roots, то есть не связан с GC Roots, то это означает, что ссылка无效а и может быть возвращена. Утечка памяти возникает из-за некоторых плохих вызовов, которые приводят к тому, что无用ные объекты и GC Roots связаны, и их нельзя回收.

Зная, что такое утечка памяти, естественно, можно понять, как с ней бороться, то есть нужно стараться избегать долгосрочных ссылок на无用ные объекты при написании кода. Saying is easy, but it requires enough experience to achieve, so memory leaks are relatively easy to occur. Since it is not easy to completely avoid them, we must be able to detect memory leaks in the program and fix them.
Теперь я расскажу, как можно обнаружить утечки памяти.

Поиск утечек памяти:

Например, вот этот код:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String string = new String();
  }
  public void click(View view){
    Intent intent = new Intent();
    intent.setClass(getApplicationContext(), SecondActivity.class);
    startActivity(intent);
  }
}
public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}

 

Каждый раз, когда происходит переход к этой Activity, вызывается поток, который затем выполняет метод run runnable. Поскольку Runnable является анонимным внутренним объектом, он имеет ссылку на SecondActivity, поэтому два Activity, которые могут быть переданы из MainActivity в SecondActivity, можно легко перейти от MainActivity к SecondActivity, затем вернуться из SecondActivity в MainActivity, и так 5 раз подряд, в конечном итоге вернувшись в MainActivity. По логике, после того как мы вернемся из SecondActivity в MainActivity, SecondActivity должна быть уничтожена и освобождена, но на практике это может не быть так.

В этот момент, чтобы�断是否发生了内存 переполнение, нужно использовать инструменты! Вот два способа

1. Используйте MAT инструмент для поиска

Сначала откройте инструмент Android Device Monitor в AS, как показано на следующем рисунке:


Откройте интерфейс, как показано на следующем рисунке


Сначала выберите имя пакета приложения, которое вы хотите проверить, и нажмите на отмеченное место на следующем рисунке, где будет помечен значок после имени пакета приложения


Далее нужно выполнить операцию с нашими приложениями, чтобы они перепрыгивали между состояниями 5 раз.

Затем нажмите на следующий значок, чтобы экспортировать файл hprof для анализа

Файл экспорта показан на следующем рисунке:

Получив файл hprof, мы можем использовать MAT инструмент для анализа

Откройте MAT инструмент

Если нет, вы можете загрузить его по следующему адресу

Адрес загрузки MAT инструмента

Интерфейс показан на следующем рисунке:

Откройте ранее экспортированный файл hprof, и, возможно, появится следующая ошибка

Поскольку MAT предназначен для анализа файлов hprof java программ, а файлы hprof, экспортированные из Android, имеют определенное форматное различие, нам нужно преобразовать экспортированные файлы hprof. В sdk предоставлен инструмент для преобразования hprof-conv.exe, как показано на следующем рисунке


Далее перейдите в этот путь и выполните команду для преобразования файла hprof, как показано на следующем рисунке:


Так используется команда hprof-conv

hprof-conv: исходный файл, файл вывода

Например, hprof-conv E:\aaa.hprof E:\output.hprof

Это означает преобразование файла aaa.hprof в output.hprof. Файл output.hprof — это наш преобразованный файл, а mat2.hprof — это файл после преобразования.

Далее откройте файл mat2.hprof, преобразованный с помощью MAT инструмента, как показано на следующем рисунке:


Затем мы можем просмотреть объекты, существующие в текущей памяти. Поскольку утечка памяти обычно происходит в Activity, нам нужно только искать Activity.

Нажмите на значок QQL, отмеченный на следующем рисунке, и введите select * from instanceof android.app.Activity

Аналогично SQL запросу для поиска информации о Activity, нажмите на красную叹号 и выполните, как показано на следующем рисунке:

next, we can see the filtered Activity information below

as shown in the figure above, there are still 6 instances of SecondActivity in memory, but we want to exit all of them, which indicates that there is a memory leak

there are two properties, Shallow size and Retained Size

Shallow Size
the memory size occupied by the object itself, excluding the objects it references. For non-array type objects, its size is the sum of the object and all its member variables.
of course, this also includes some data storage units of java language features. For array type objects, its size is the total size of array element objects.
Retained Size
Retained Size = current object size + total size of objects that can be directly or indirectly referenced by the current object. (The meaning of indirect reference: A->B->C, C is indirect reference)
however, when releasing, we still need to exclude objects that are directly or indirectly referenced by GC Roots. They will not be considered as garbage temporarily.

next, right-click on a SecondActivity


select with all references

open the page as shown in the figure below

view the page in the figure below

see that this0 refers to thisActivitywhilethis0 represents the meaning of an inner class, that is, an inner class refers to Activity, and this$0 is again referenced by target. Target is a thread. The cause has been found. The reason for the memory leak is that Activity is referenced by the inner class, and the inner class is used by the thread, so it cannot be released. Let's turn to the code of this class.

public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}
Действительно, в

Действительно, в SecondActivity существует объект внутреннего класса Runnable, который затем используется потоком, и поток должен выполнить 8000 секунд, поэтому объект SecondActivity ссылаются и не может быть освобожден, что привело к memory overflow.

Чтобы решить эту проблему memory overflow, нужно своевременно завершить поток при выходе из Activity (хотя это не очень хорошо заканчивать..), или хорошо контролировать время выполнения потока.

Таким образом, мы нашли memory overflow в этом программ.

2. Прямо использовать Monitor Memory в Android Studio для поиска memory overflow

Вновь используя этот же программ, я упростил объяснение.

Сначала запустите программу на телефоне, откройте интерфейс Monitor в AS, чтобы просмотреть Memory diagrams

Нажмите на иконку маленького грузовика (иконка в позиции 1 на diagrams), чтобы запустить один GC


Нажмите на иконку в позиции 2 на diagrams, чтобы просмотреть файл hprof

Слева - объекты в памяти, внутри ищите Activity, существует ли мы хотим уже освобожденный Activity, если出现 мы ожидаем уже освобожденный Activity, нажмите, и это покажет его общее количество в правой части, нажмите на любую из правых частей, можно показать его дерево GC Roots, просмотр diagrams можно найти место возникновения memory leak (аналогично первому способу)

Таким образом, мы完成了 memory leak поиска.

Причина возникновения memory leak в Android大致 делится на следующие несколько типов:

1. Memory leak, вызванное статическими переменными

Поскольку жизненный цикл статических переменных начинается с загрузки класса и заканчивается его выгрузкой, то есть статические переменные высвобождаются только при死亡 процесса программы, если в статических переменных вызывается Activity, то эта Activity, так как она ссылаются, будет иметь такой же жизненный цикл, как и статические переменные, и не сможет быть освобождена, что приведет к memory leak.

Решение:

Когда Activity ссылаются на статические переменные, используйте getApplicationContext, потому что жизненный цикл Application с начала программы до ее окончания такой же, как и у статических переменных.

2. Memory leak, вызванное потоком

Аналогично ситуации в приведенном выше примере, время выполнения потока очень долго, и даже если Activity выйдет, он все равно будет выполняться, потому что поток или Runnable является внутренним классом Activity, поэтому он имеет доступ к экземпляру Activity (поскольку создание внутреннего класса требует внешнего класса), что приводит к тому, что Activity не может быть освобождена.

AsyncTask имеет пул потоков, проблема более серьезная

Решение:

1. Рационально планируйте время выполнения потоков, контролируйте, чтобы потоки завершались до завершения Activity.

2. Измените внутренний класс на статический внутренний класс и используйте WeakReference для сохранения экземпляра Activity, так как слабая ссылка回收able, если GC обнаруживает ее, он ее回收, поэтому можно быстро回收

3. Избыточное использование памяти bitmap

Разбор bitmap требует памяти, но память предоставляет только 8M пространства для BitMap, если изображений много и bitmap не recycle своевременно, то может произойти переполнение памяти.

Решение:

Своевременно recycle сжатые изображения перед их загрузкой

4. Утечка памяти, вызываемая несвоевременным закрытием ресурсов

Например, некоторые Cursor не закрываются своевременно, сохраняя ссылку на Activity, что может привести к утечке памяти

Решение:

В методе onDestory необходимо своевременно close

5. Утечка памяти, вызываемая использованием Handler

Поскольку handler отправляет объект message в MessageQueue, а затем Looper опрашивает MessageQueue и выполняет Message, если Message долго не выполняется, то из-за ссылки на Handler в Message, а Handler, как правило, является внутренним классом объекта, Message ссылается на Handler, а Handler ссылается на Activity, что делает Activity недоступной для сбора мусора.

Решение:

Использование статического внутреннего класса + слабых ссылок может решить проблему

Некоторые из них связаны с проблемами, такими как не удаленные объекты набора, не отписанные объекты, проблемы с давлением кода, которые могут привести к утечке памяти, но обычно решения, упомянутые выше, могут решить эту проблему.

Основной учебник
Тебе может понравиться