Понимание Управления Памятью В Современных Языках Программирования

Привет, Хабр! Представляю вашему вниманию перевод статьи « Демистификация управления памятью в современных языках программирования » Дипу К.

Сасидхарана.

В этой серии статей я хотел бы развеять мифы об управлении памятью в программном обеспечении (далее — ПО) и подробно рассмотреть возможности, предоставляемые современными языками программирования.

Надеюсь, что мои статьи помогут читателю заглянуть под капот этих языков и узнать для себя что-то новое.

Углубленное изучение концепций управления памятью позволяет писать более эффективное программное обеспечение, поскольку стиль и практика кодирования оказывают большое влияние на то, как память распределяется для нужд программы.



Часть 1. Введение в управление памятью

Управление памятью — это целый набор механизмов, позволяющих контролировать доступ программы к оперативной памяти компьютера.

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



Для чего используется ОЗУ?

Когда программа работает в операционной системе компьютера, ей необходим доступ к оперативной памяти (ОЗУ), чтобы:
  • загрузить свой собственный байт-код для выполнения;
  • хранить значения переменных и структур данных, которые используются во время работы;
  • загружать внешние модули, необходимые программе для выполнения задач.

Помимо места, используемого для загрузки собственного байт-кода, программа при работе использует две области оперативной памяти — куча (стек) и куча (куча).



Куча

Стек используется для распределения статической памяти.

Он организован по принципу «последним пришёл — первым ушёл» ( ЛИФО ).

Вы можете думать о стопке как о стопке книг — вам разрешено взаимодействовать только с самой верхней книгой: читать ее или положить на нее новую.

  • Благодаря упомянутому принципу стек позволяет очень быстро выполнять операции с данными — все манипуляции производятся с «верхней книгой в стеке».

    Книга добавляется в самый верх, если данные нужно сохранить, или берется сверху, если данные нужно прочитать;

  • есть ограничение в том, что данные, которые предполагается хранить в стеке, должны быть конечными и статичными — их размер должен быть известен на этапе компиляции;
  • Память стека хранит стек вызовов — информацию о ходе выполнения цепочек вызовов функций в виде кадров стека.

    Каждый кадр стека представляет собой набор блоков данных, в которых хранится информация, необходимая для работы функции на определенном шаге — ее локальные переменные и аргументы, с которыми она была вызвана.

    Например, каждый раз, когда функция объявляет новую переменную, она добавляет ее в верхний блок стека.

    Затем, когда функция завершается, все блоки памяти в стеке, которые использовала функция, очищаются — другими словами, все блоки в ее кадре стека очищаются;

  • Каждый поток в многопоточном приложении имеет доступ к собственному стеку;
  • Управление стековой памятью простое и понятное; он выполняется операционной системой;
  • В стеке обычно хранятся такие данные, как локальные переменные и указатели;
  • При работе со стеком существует вероятность получения ошибок переполнения стека, так как его максимальный размер строго ограничен.

    Например, ошибка при построении граничного условия в рекурсивной функции наверняка приведет к переполнению стека;

  • Большинство языков имеют ограничение на размер значений, которые могут храниться в стеке;


Понимание управления памятью в современных языках программирования

Использование стека в JavaScript. Объекты хранятся в куче, и доступ к ним осуществляется по ссылкам, хранящимся в стеке.

Здесь можно посмотреть в видео формате

Куча

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

Вы можете себе представить, что куча — это такая большая многоуровневая библиотека, в которой, следуя определенным инструкциям, можно найти нужную книгу.

  • операции над кучей выполняются несколько медленнее, чем над стеком, так как требуют дополнительного шага поиска данных;
  • в куче хранятся данные динамического размера, например список, в который можно добавлять произвольное количество элементов;
  • куча является общей для всех потоков приложения;
  • Из-за своей динамической природы управлять кучей непросто, и она ответственна за большинство всех проблем и ошибок, связанных с памятью.

    Способы решения этих проблем предоставляют языки программирования;

  • Типичными структурами данных, хранящимися в куче, являются глобальные переменные (они должны быть доступны различным потокам приложения, а куча является общей для всех потоков), ссылочные типы, такие как строки или ассоциативные массивы, а также другие сложные структуры данных;
  • При работе с кучей можно получить ошибки нехватки памяти, если приложение пытается использовать больше памяти, чем ему доступно;
  • Размер значений, которые можно хранить в куче, ограничен только общим объемом памяти, который операционная система выделила для программы.



Почему важно эффективное управление памятью?

В отличие от жестких дисков, объем оперативной памяти очень ограничен (хотя и жесткие диски, конечно, не безграничны).

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

Тогда он просто выйдет из строя сам по себе или, что более драматично, приведет к сбою операционной системы.

Поэтому не рекомендуется легкомысленно относиться к манипуляциям с памятью при разработке программного обеспечения.



Различные подходы

Современные языки программирования стараются максимально упростить работу с памятью и избавить разработчиков от части головной боли.

И хотя некоторые почтенные языки по-прежнему требуют ручного управления, большинство из них по-прежнему предоставляют более элегантные автоматизированные подходы.

Иногда в языке используется несколько подходов к управлению памятью, а иногда разработчик может даже выбрать, какой вариант будет более эффективным конкретно для его задач (хороший пример — С++ ).

Перейдем к краткому обзору различных подходов.



Ручное управление памятью

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

Выделение и освобождение памяти для создаваемых объектов остается полностью на усмотрении разработчика.

Примером такого языка является С .

Он предоставляет несколько методов ( маллок , перераспределить , каллок И бесплатно ) для управления памятью — разработчик должен использовать их для выделения и освобождения памяти в своей программе.

Такой подход требует большой осторожности и внимания.

Это также особенно сложно для новичков.



Уборщик мусора

Сбор мусора — это процесс автоматического управления памятью в куче, заключающийся в поиске неиспользуемых областей памяти, ранее занятых потребностями программы.

Это один из самых популярных механизмов управления памятью в современных языках программирования.

Процедура сбора мусора обычно запускается через заранее определенные интервалы времени, а иногда выполняется одновременно с ресурсоемкими процессами, что приводит к задержкам в работе приложения.

JVM ( Джава / Скала / классный / Котлин ), JavaScript , Питон , С# , Голанг , OCaml И Рубин — это примеры популярных языков, использующих сборку мусора.

  • Сборщик мусора на основе алгоритма тегирования (Mark & Sweep): это алгоритм, который работает в два этапа: сначала он помечает в памяти объекты, на которые имеются ссылки, а затем освобождает память от объектов, не получивших меток.

    Этот подход используется, например, в JVM, C#, Ruby, JavaScript и Golang. JVM имеет на выбор несколько различных алгоритмов сборки мусора, а механизмы JavaScript, такие как V8, используют алгоритм маркировки в дополнение к подсчету ссылок.

    Такой сборщик мусора может быть включен в C и C++ как внешняя библиотека.



    Понимание управления памятью в современных языках программирования

    Визуализация алгоритма тегирования: объекты, связанные ссылками, тегируются, а затем удаляются недоступные.

  • Сборщик мусора с подсчетом ссылок : для каждого объекта в куче ведется счетчик обращений к нему — если счетчик достигает нуля, то память освобождается.

    Этот алгоритм в чистом виде не способен корректно обрабатывать циклические обращения объекта к самому себе.

    Сборщик мусора с подсчетом ссылок, а также дополнительные приемы для обнаружения и обработки циклических ссылок используются, например.

    PHP , Перл И Питон .

    Этот алгоритм сборки мусора также можно использовать в С++ ;



Получение ресурса — это инициализация (RAII)

РАИИ — это идиома программирования в ООП, смысл которой заключается в том, что область памяти, выделяемая для объекта, строго привязана к его времени жизни.

Память выделяется в конструкторе и освобождается в деструкторе.

Впервые этот подход был реализован в С++ , а также используется в Ада И Ржавчина .



Автоматический подсчет ссылок (ARC)

Этот подход очень похож на сборку мусора с подсчетом ссылок, однако вместо запуска процесса подсчета через определенные интервалы инструкции выделения и освобождения памяти вставляются непосредственно в байт-код во время компиляции.

Когда счетчик ссылок достигает нуля, память освобождается в рамках обычного потока выполнения программы.

Автоматический подсчет ссылок по-прежнему не обрабатывает циклические ссылки и требует от разработчика использования специальных ключевых слов для дополнительной обработки таких ситуаций.

АРК это одна из особенностей переводчика Кланг , поэтому присутствует в языках Цель-C И Быстрый .

Автоматический подсчет ссылок также доступен для использования в Ржавчина и новые стандарты С++ С помощью умные указатели .



Владение

Эта комбинация РАИИ с концепцией владения, согласно которой каждое значение в памяти должно иметь только одну переменную-владельца.

Когда владелец покидает зону исполнения, память тут же освобождается.

Можно сказать, что это что-то вроде подсчета ссылок на этапе компиляции.

Этот подход используется в Ржавчина мне также не удалось найти какой-либо другой язык, использующий аналогичный механизм.



Понимание управления памятью в современных языках программирования




В этой статье были рассмотрены основные понятия в области управления памятью.

Каждый язык программирования использует свои реализации этих подходов и алгоритмов, оптимизированных для различных задач.

В следующих частях мы подробнее рассмотрим решения по управлению памятью на популярных языках.

Читайте также другие части серии:



Ссылки




Если вам понравилась статья, пожалуйста, проголосуйте за нее или напишите комментарий.

Вы можете подписаться на автора статьи по адресу Твиттер и дальше LinkedIn .

Иллюстрации: Визуализация стека выполнена с использованием репетитор по Python .

Иллюстрация понятия собственности: Линк Кларк, команда Rust под Лицензия Creative Commons с указанием авторства, версия 3.0 .

Отдельное спасибо за корректуру перевода.

Александр Максимовский И Катерина Шибакова Теги: #программирование #информатика #управление памятью #утечки памяти

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.