За что мы любим языки с автоматической сборкой мусора ? За то, что нам с вами приходится меньше думать. Мы просто создаем новые объекты, а когда они оказываются ненужны, RTS сама освобождает память. Проблема утечки памяти решена, жизнь прекрасна и удивительна! Вот только, к сожалению, это неправда.

Автоматическая у вас сборка мусора или нет, память в ваших программах все равно течет.

Java. Рассмотрим замечательный пример со стеком из книги Effective Java. Можете ли вы обнаружить в нем утечку памяти? Не спешите читать дальше, посмотрите на пример и подумайте… Я серьезно , это очень интересное упражнение. Ладно, рассказываю. Поскольку ссылки в массиве elements не обнуляются в методе pop, объекты, однажды помещенные в стек, не будут освобождены до тех пор, пока стек не будет уничтожен. Что намного хуже, объекты, на которые ссылаются объекты, однажды помещенные в стек, также будут висеть в памяти, пока существует стек. Или пока ссылки в стеке не будут перезаписаны другими ссылками.

Go и другие. Течет по той же причине, по которой течет и Java. Вообще, пример со стеком применим к любому языку с автоматической сборкой мусора , если в нем есть изменяемые переменные.

Python. Создадим два объекта, имеющих деструкторы. Сошлемся из первого объекта на второй, а из второго на первый. Перестанем использовать объекты. Вопрос — какой объект будет уничтожен первым? Допустим, первый. Но что, если второй объект использует первый в своем деструкторе? Все сломается! Поэтому в такой ситуации Python не освобождает память. Оба объекта будут висеть в памяти до тех пор, пока работает программа. А если таких цепочек объектов создается много, программа быстро упадет с out of memory.

Erlang. Известен своей способностью создавать мемори лики при работе с большими кусками бинарных данных. Допустим, есть бинарь размером 1 Мб. Нам нужны первые 3 байта из него. Мы можем легко получить их при помощи сопоставления с образцом. Но физически это те же первые три байта из 1 Мб данных. Даже если весь бинарь целиком не используется, выделенные 1 Мб памяти не могут быть освобождены. Решить эту проблему можно, используя фукнцию binary:copy() . Однако бездумно копировать все и вся не только дорого, но и может приводить к еще большим мемори ликам, если оригинальные 1 Мб все-таки используются! А еще в Erlang постоянно переполняются очереди .

Haskell. Все бояться хаскеля из-за ленивых вычислений . И, возможно, не зря. Допустим, мы написали рекурсивную функцию с аргументом-аккумулятором. Будучи ленивым языком, Haskell будет постоянно откладывать вычисление этого аргумента. Вместо вычисления, язык будет выделять все больше и больше памяти, содержащей информацию о том, как произвести эти вычисления. Анализ строгости (strictness analysis) отчасти решает эту проблему. Но факт остается фактом, при использовании ленивых вычислений приходится дополнительно думать о том, чтобы память не утекла.

Как видите, не важно, на императивном языке вы пишите или функциональном, строгом или ленивом, память при использовании GC все равно утекает. А раз так, может быть вообще отказаться от использования GC? Например, давайте использовать в программе умные указатели (которые со счетчиками ссылок), а для особых случаев, вроде создания двусвязных списков, обычные указатели.

Мы сразу лишаемся такого количества проблем! Никаких накладных расходов на GC, никаких stop the world ! Программа работает быстро, словно если бы мы писали на C++, память расходуется почти так же экономно, как если бы мы выделяли ее вручную! Кроме того, в нашем арсенале появляются такие приемы, как «а давайте уместим наши данные в L1 кэш».

Чем за это приходится платить? Если у нас появятся циклические ссылки, память утечет. Но так она может утечь и при использовании полноценного GC! Так что тут нет особой разницы. Вот проблема фрагментации памяти куда актуальнее. Но давайте подумаем, а многие ли из нас сталкивались с этой проблемой на практике? Лично я, кажется, еще ни разу. А ведь мы ежедневно используем кучу программ, написанных на C/C++. Ну и потом, хоть я и не специалист в этом вопросе, сдается мне, что проблема фрагментации памяти сравнительно легко решается при помощи повторного использования объектов, приемов вроде slab allocator’ов и так далее. Если вдруг вы с ней столкнетесь.

В общем, идея написания в третьем тысячелетии программ без использования полноценного GC, как предлагается в языках вроде Rust или Vala , не кажется такой уж безумной.

Дополнение: Некто Index Int подсказал в комментариях ссылку на отличную статью по теме. В частности, в ней объяснено, почему молодые объекты выгоднее собирать с помощью GC, а старые — при помощи счетчиков ссылок. Следует однако отметить, что в Rust молодые объекты размещаются на стеке и быстро освобождаются.

admin

Share
Published by
admin

Recent Posts

Apple: история логотипа

Как менялся логотип Apple на протяжении многих лет. Логотип Apple — это не просто символ,…

6 дней ago

Security Boot Fail при загрузке Acer — решение проблемы

Security Boot Fail при загрузке Acer — решение проблемы При загрузке ноутбука Acer с флешки,…

3 недели ago

Ноутбук не включается — варианты решения

Ноутбук не включается — варианты решения Если при попытке включить ноутбук вы обнаруживаете, что он…

3 недели ago

The AC power adapter wattage and type cannot be determined — причины и решение

The AC power adapter wattage and type cannot be determined — причины и решение При…

3 недели ago

Свистит или звенит блок питания компьютера — причины и решения

Свистит или звенит блок питания компьютера — причины и решения Некоторые владельцы ПК могут обратить…

3 недели ago

Мигает Caps Lock на ноутбуке HP — почему и что делать?

Мигает Caps Lock на ноутбуке HP — почему и что делать? При включении ноутбука HP…

3 недели ago