Статья о необходимости освобождения объектов в определенных ситуациях.
Одна из важнейших особенностей Java и Kotlin — автоматическое управление памятью, когда сборщик мусора сам удаляет неиспользуемые объекты из оперативной памяти. Сборщик мусора существенно упрощает жизнь разработчикам, но он не всегда работает.
РЕКОМЕНДУЕМ:
- Оптимизация загрузки приложений в Kotlin
- Современный подход к разработке с использованием Kotlin
- Как сделать код на Kotlin более понятным
Канонический пример, когда сборщик мусора не справляется, — это активности Android. Рассмотрим следующий пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //... logError = { Log.e( this::class.simpleName, it.message ) } } //... companion object { var logError: ((Throwable) -> Unit)? = null } } |
Разработчик решил упростить себе жизнь и вынес функцию logError в companion object, чтобы всегда иметь к ней доступ. Проблема здесь только в том, что logError внутри себя ссылается на на MainActivity. В итоге, если MainActivity будет завершена, она продолжит занимать память — сборщик мусора не сможет ее освободить по причине существования ссылки на активность в объекте‑компаньоне.
Решить эту проблему можно, заменив жесткую ссылку на WeakReference (хотя по‑хорошему проблема решается с помощью системы внедрения зависимостей).
Еще один пример — реализация стека:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Stack { private var elements: Array<Any?> = arrayOfNulls(DEFAULT_INITIAL_CAPACITY) private var size = 0 fun push(e: Any) { ensureCapacity() elements[size++] = e } fun pop(): Any? { if (size == 0) { throw EmptyStackException() } return elements[--size] } private fun ensureCapacity() { if (elements.size == size) { elements = elements.copyOf(2 * size + 1) } } companion object { private const val DEFAULT_INITIAL_CAPACITY = 16 } } |
Она абсолютно корректна, за исключением одного момента: функция pop() не удаляет элемент из массива. Это значит, что если создать стек из 1000 элементов, а затем удалить 999 из них, стек все равно будет занимать память, необходимую для хранения 1000 элементов. Исправляется код вот так:
1 2 3 4 5 6 7 |
fun pop(): Any? { if (size == 0) throw EmptyStackException() val elem = elements[--size] elements[size] = null return elem } |
Как обнаружить такие проблемы? Стоит научиться пользоваться анализатором хипа (такой есть в стандартной поставке Android Studio). Также поможет инструмент LeakCanary. Он автоматически оповестит обо всех утечках активностей.
Effective Kotlin Item 50: Eliminate obsolete object references