Никому не нравятся креши с OutOfMemoryError
Работая над Square Register, мы рисуем подпись клиента используя битмап-кеш. Поскольку этот битмап размером с экран устройства, у нас было очень много OutOfMemory крешей во время создания его.
Мы пробовали несколько подходов ни один из которых не решил проблему:
- Использовали 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);
}
}
Вы получаете уведомление в неплохом виде:
Заключение
После начала использования LeakCanary, мы обнаружили и исправили много утечек в нашем приложении. Мы даже нашли несколько утечек в Android SDK.
Результаты потрясающие: на 94% меньше OutOfMemoryError крешей!
Если вы хотите избавиться от OOM крешей, установи LeakCanary сейчас!