Если вы знаете, что такое «подсчет ссылок», думаю, вы можете пропустить эту статью. Я собираюсь использовать C# и говорить о C#. Однако то, что мы здесь обсуждаем, применимо и к другим языкам.

Я занимаюсь разработкой приложений на С# уже много лет, и я столкнулся со слишком многими случаями, когда не понимал управление памятью на С#.

Большинство, если не все, знают о сборщике мусора. Но сколько на самом деле понимают GC? Если бы я попросил вас написать код, который приводит к утечке памяти, смогли бы вы это сделать?! В интервью, когда я прошу людей рассказать мне, как происходит утечка памяти в C#, я получаю следующий:

  1. Используйте неуправляемый код
  2. Не закрывая БД или сетевые соединения
  3. Подписки на события C#

и ИМХО, эти ответы говорят мне, что вы запомнили кучу вопросов для собеседования, не понимая толком GC C#. «Неуправляемый код» особенно раздражает, поскольку более 95 % разработчиков C# этого не делают, и им никогда не приходилось писать неуправляемый код. В C# очень легко утечь память, если вы понимаете подсчет ссылок. Так что же такое подсчет ссылок?

Подсчет ссылок

На высоком уровне: для каждого объекта в памяти существует целое число, которое отслеживает, сколько других объектов могут достичь этого объекта. Например: если внутри метода вы создаете новый объект, этот объект будет доступен в этом методе, поэтому для ссылки будет установлено значение 1, после выхода из этого метода этот объект больше не будет доступен из любого места в ваш код, поэтому количество ссылок упадет до нуля. Сборщик мусора ищет объекты или группы объектов, у которых счетчик ссылок равен нулю. Обратите внимание, как я сказал «группа»; это было преднамеренно! Представьте, что на изображении ниже каждый узел является объектом в памяти, а ребра — ссылками. Если бы мы удалили ссылку между 2 и 4, то объекты 4,8,9 больше не были бы доступны из корня и поэтому могли быть удалены из памяти сборщиком мусора.

Хорошо, хватит разговоров, давайте посмотрим на код!

Простое использование памяти

Если мы запустим приведенный выше код, мы получим следующий вывод

Старт!
Размер списка=0. Память = 0,03 МБ
Размер списка = 1 000 000. Mem = 15,74 МБ
Из основного метода непосредственно перед строкой чтения Mem = 0,03 МБ

1-й раз, когда мы печатаем память, составляет 0,03 МБ. 2-й раз, когда мы печатаем, память +15 МБ и 3-й раз, когда мы печатаем, память снова уменьшается до 0,03 МБ. читайте комментарии внутри кода для получения дополнительной информации.

Как вы думаете, что будет, если вместо «void» вернуть список? Если вы сказали: память НЕ будет освобождена, а последний оператор печати покажет нам, что используется +15 МБ памяти, вы правы! Когда мы возвращаем список, счетчик ссылок на список по-прежнему равен 1, потому что он доступен из основного метода.

Неправильно понятые события и делегаты C#

В интервью люди говорят, что события C# являются основной причиной утечек памяти. «Обязательно отпишитесь, иначе у вас будет утечка воспоминаний»! ИМХО, это утверждение наполовину верно и показывает, что человек не до конца понимает, как работает сборщик мусора C#, и просто цитирует ответы на интервью. Вы же не слышите, как люди говорят: «не забудьте установить для свойства reference значение null после того, как закончите с ним, иначе произойдет утечка памяти», верно?! давайте взглянем на приведенный ниже пример, где мы НЕотписываемся, но у нас нет утечки памяти.

Если мы запустим приведенный выше код, мы увидим, что память до и после вызова метода практически одинакова; мы видим всплеск памяти внутри метода, но это ожидаемо, мы создаем 100 тыс. объектов.

Начальный основной, 0,03 МБ
Конец второго метода, 4,81 МБ
Конечный основной, 0,04 МБ

Итак, с приведенным выше кодом, который мы продемонстрировали, речь идет не о подписке или отмене подписки, а о подсчете ссылок! Я ни в коем случае не призываю «не отписываться», вы всегда должны отменять подписку всякий раз, когда это имеет смысл, и соответствующим образом очищать ресурсы.

Сводка

В зависимости от типа приложения, над которым вы работаете, и от того, как оно было разработано, вы столкнетесь с различными причинами утечки памяти. Например, если вы работаете над кодом, основанным на событиях, есть вероятность, что в вашем пользовательском интерфейсе происходит утечка памяти, потому что кто-то забыл отписаться. Если вы используете много статических классов или синглтонов, будьте осторожны — с ними очень легко происходит утечка памяти. Если вы добавляете элементы в коллекцию, пока коллекция существует и что-то содержит ссылку на нее, эти элементы останутся в памяти! Подумайте об управлении памятью с точки зрения подсчета ссылок и доступности вашего кода из «стека».

Спасибо, что прочитали эту статью