Никому не нравятся креши с OutOfMemoryError


Работая над Square Register, мы рисуем подпись клиента используя битмап-кеш. Поскольку этот битмап размером с экран устройства, у нас было очень много OutOfMemory крешей во время создания его.

image

Мы пробовали несколько подходов ни один из которых не решил проблему:
  • Использовали Bitmap.Config.ALPHA_8
  • Ловили OutOfMemoryError, вызывали сборку мусора и пробовали снова (подглядели в GCUtils),
  • Мы не рассматривали вариант с размещением битмапов вне кучи Java. К счастью Fresco еще не существовало,



Мы шли по ложному пути



Проблема была не в размере битмапа. Когда память почти полностью использована OOM может возникнуть где угодно. Конечно, вероятность что это произойдет во время выделения больших объектов, вроде битмапов, увеличивается. OOM — это лишь симптом более глубокой проблемы: утечек памяти.

Что такое утечка памяти?



Многие объекты имеют ограниченное время жизни. Когда их время жизни заканчивается, ожидаются что они будут собранны сборщиком мусора. Если цепочка ссылок указывает на объект в памяти, после ожидаемого завершения времени жизни объект, то это создает утечку памяти. При накоплении некоторого количества утечек, у программы начинает заканчивается память.

Например, после того как был вызван Activity.onDestroy, активити, вся ииерархия вьюшек, и связанных с ними битмапов должны быть доступны для сборки мусора. Если фоновый поток во время работы удерживает ссылку на активити, то память занимаемая ей память не может быть освобождена. Рано или поздно это приведет к выбросу OutOfMemoryError.

Охота на утечки



Охота на утечки памяти ручной процесс, хорошо описанный в серии статей Wrangling Dalvik от Raizlabs.

Вот ключевые этапы:

  • Узанть о крешах OutOfMemoryError через Bugsnag, Crashlytics или консоль разработчика
  • Попытаться воспроизвести проблему. Возможно вам понадобится купить, одолжить или украсть конкретное устройство подверженное крешам (Не на всех девайсах проявляются все утечки!). Также необходимо восстановить последовательность действий приводящих к утечке, возможно грубым перебором.
  • Сделать дамп памяти при OutOfMemoryError (Здесь можно узнать как).
  • Изучить дамп памяти с помощью MAT или YourKit и обнаружить объекты которые должны были быть собраны сборщиком мусора.
  • Найти самые короткие пути ссыльных ссылок от объекта до корней сборщика мусора (GC Roots).
  • Найти ссылку которой не должно быть и исправить утечку памяти.


Что если существовала бы библиотека которая могла сделать это все, еще до возникновения OOM и позволила бы сосредоточится на исправлении утечки памяти?

Представляем LeakCanary



LeakCanary — это Open Source Java библиотека для обнаружения утечек памяти в отладочных сборках.

Давайте взглянем на следующий пример:

class Cat {
}
class Box {
  Cat hiddenCat;
}
class Docker {
  static Box container;
}

// ...

Box box = new Box();
Cat schrodingerCat = new Cat();
box.hiddenCat = schrodingerCat;
Docker.container = box;


Затем вы создаете объект RefWatcher и предоставляете ему объект для отслеживания:

// Мы ожидаем что schrodingerCat должен исчезнуть (или нет). Давайте отслеживать его.
refWatcher.watch(schrodingerCat);


После того как утечка была обнаружена, вы получаете неплохой трейс утечки.
* GC ROOT static Docker.container
* references Box.hiddenCat
* leaks Cat instance


Поскольку мы знаем, что вы то и дело занимаетесь новыми фичами, мы сделали установку очень простой. С помощью всего одной строчки кода, LeakCanary автоматически будет обнаруживать утечки активити:

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}


Вы получаете уведомление в неплохом виде:

image

Заключение



После начала использования LeakCanary, мы обнаружили и исправили много утечек в нашем приложении. Мы даже нашли несколько утечек в Android SDK.

Результаты потрясающие: на 94% меньше OutOfMemoryError крешей!

image

Если вы хотите избавиться от OOM крешей, установи LeakCanary сейчас!

Комментарии (7)


  1. VioletGiraffe
    10.05.2015 20:23

    Интересная работа! Сомневаюсь, что смогу интегрировать в своё приложение, но попробую.


    1. danikula
      13.05.2015 10:31

      Интегрируется буквально несколькими строками. Я начал использовать и уже получил полезные данные.


  1. 7voprosov
    10.05.2015 23:09
    +3

    Square в очередной раз двигают android разработку вперед к светлому будущему!


  1. n4k4m1sh1
    11.05.2015 07:57

    Интересно: она также эффективна с ARTкак и с Dalvik?


    1. serso
      11.05.2015 12:10

      Да, там просто подсчёт ссылок идёт.


  1. kemsky
    11.05.2015 14:18

    Я так понимаю объекты трекаются через weakreference, а затем проводится поиск в heap dump. Делал тоже самое для флеша, но там нельзя было получить дамп, а нужно было запускать профайлер и воспроизводить.


  1. HeavyRazzer
    15.05.2015 18:16

    Очень полезный модуль. У нас оно в дебажной версии нашего приложения еще кидает свои логи в HipChat, так что все могут в реальном времени смотреть, как плохеет новой сборке во время активного тестирования.