введение
ПреамбулаМы популяризировали некоторые вещи о сборке мусора и получили общее представление о концепциях и процессах сборки мусора.Процесс запуска приложенияВ этой статье мы упоминали, что каждое приложение в Android по умолчанию запускается в независимом процессе, и этот независимый процесс представляет собой процесс виртуальной машины, созданный из Zygote. Другими словами, каждое приложение выполняется в независимом процессе. Пространство ВМ. Итак, как Android управляет памятью этих приложений и как управляет памятью этих независимых ВМ?
Сегодня мы поговорим об управлении памятью в Android.
Шенандоа
Shenandoah — новый GC, выпущенный как часть JDK 12. Ключевое преимущество Shenandoah перед G1 состоит в том, что большая часть цикла сборки мусора выполняется одновременно с потоками приложений. G1 может эвакуировать области кучи только тогда, когда приложение приостановлено, а Shenandoah перемещает объекты одновременно с приложением.
Shenandoah может компактировать живые объекты, очищать мусор и освобождать оперативную память почти сразу после обнаружения свободной памяти. Поскольку все это происходит одновременно, без приостановки работы приложения, то Shenandoah более интенсивно нагружает процессор.
Аргумент JVM для сборщика мусора Шенандоа: .
Используйте приложение, чтобы очистить Mac RAM
Как мы уже говорили выше, нет необходимости устанавливать стороннее приложение для управления ОЗУ, поскольку macOS сама по себе должна выполнять достаточно хорошую работу. Но если, например, ваш Mac не имеет столько оперативной памяти, сколько вам нужно, то стороннее приложение может оказаться дешевле и дешевле, чем пытаться самостоятельно добавить больше оперативной памяти.
Мы рекомендуем вам использовать инструмент из Mac App Store, так как вы можете быть уверены, что он одобрен Apple.
Вот несколько приложений, которые вы можете попробовать:
Parallels Tool Box
Это приложение предлагает вам более 30 инструментов, включая инструмент свободной памяти. Преимущество в том, что вы получаете множество других удобных инструментов, таких как Поиск дубликатов, так что вы можете удалять ненужные вещи, освобождать место, простой способ делать скриншоты и записывать видео, а также загружать больше.
Когда мы запустили его, мы восстановили более 1 ГБ памяти. Здесь доступна бесплатная пробная версия, или она стоит £ 15,99 в год.
Memory Clean 2 от Fliplab
Это очистит неактивную память вашего Mac — то, что вы, возможно, захотите сделать после закрытия особенно интенсивного приложения или игры. Некоторые функции доступны через бесплатное приложение, но вы можете приобрести дополнительные инструменты в приложении. Когда мы запустили это освободилось около 1 ГБ. Загрузите это здесь.
Висячие указатели
C++ не дает никаких гарантий относительно того, что произойдет с содержимым освобожденной памяти или со значением удаляемого указателя. В большинстве случаев память, возвращаемая операционной системе, будет содержать те же значения, которые были до ее возврата, а указатель продолжит указывать на уже освобожденную память.
Указатель, указывающий на освобожденную память, называется висячим указателем. Косвенное обращение или удаление висячего указателя приведет к неопределенному поведению. Рассмотрим следующую программу:
В приведенной выше программе значение 7, которое ранее было присвоено выделенной памяти, вероятно, всё еще будет там; но возможно, что значение по этому адресу памяти могло измениться. Также возможно, что память может быть уже выделена другому приложению (или для собственного использования операционной системой), и попытка доступа к этой памяти приведет к тому, что операционная система завершит работу программы.
Освобождение памяти может привести к появлению нескольких висячих указателей. Рассмотрим следующий пример:
Здесь могут помочь несколько передовых практик.
Во-первых, постарайтесь избегать того, чтобы несколько указателей указывали на один и тот же фрагмент динамической памяти. Если это невозможно, четко определите, какой указатель «владеет» памятью (и отвечает за ее удаление), а кто просто обращается к ней.
Во-вторых, при удалении указателя, если этот указатель не выходит за пределы области сразу после этого, установите указатель на 0 (или в C++11). Мы поговорим подробнее о нулевых указателях и о том, почему они полезны, чуть позже.
Лучшая практика
Устанавливайте удаленные указатели на 0 (или в C++11), если они сразу после этого не выходят из области видимости.
Word tearing
Некоторые процессоры не позволяют записывать один байт в ОЗУ, что приводит к проблеме, называемой word tearing. Представьте, что у нас есть массив байт. Один поток записывает первый байт, а второй поток пытается записать значение в рядом стоящий байт. Но если процессор не может записать один байт, а только целое машинное слово, то запись рядом стоящего байта может быть проблематичной. Если просто считать машинное слово, обновить один байт и записать обратно, то мы помешаем другому потоку.
В JVM нет проблемы word tearing. Два потока, пишущие рядом стоящие байты не должны мешать друг другу.
Действия
Inter-thread action (термин такой, не знаю, как перевести, может, межпоточное действие?) — это действие внутри одного потока, которое может повлиять или быть замечено другим потоком. Существует несколько типов inter-thread action:
- Чтение (нормальное, не volatile). Чтение переменной.
- Запись (нормальная, не volatile). Запись переменной.
- volatile read. Чтение volatile переменной.
- volatile write. Запись volatile переменной.
- Lock. Взятие блокировки монитора.
- Unlock. Освобождение блокировки монитора.
- (синтетические) первое и последнее действие в потоке.
- Действия по запуску нового потока или обнаружения остановки потока.
- Внешние действия. Это действия, которые могут быть обнаружены снаружи выполняющегося потока, например, взаимодействия с внешним окружением.
- Thread divergence actions. Действия потока, находящегося в бесконечном цикле без синхронизаций, работы с памятью или внешних действий.
Объекты Python в памяти
Как мы знаем, в Python все является объектом. Объект может быть простым(содержащий числа, строки и т. д.) или контейнером(словарь, списки или определенные пользователем классы). В Python нам не нужно объявлять переменные или их типы перед их использованием в программе.
Давайте разберемся в следующем примере.
a= 10 print(a) del a print(a)
Выход:
10 Traceback(most recent call last): File "", line 1, in print(x) NameError : name 'a' is not defined
Как видно из вышеприведенного вывода, мы присвоили значение объекту x и распечатали его. Когда мы удаляем объект x и пытаемся получить доступ в дальнейшем коде, будет ошибка, утверждающая, что переменная x не определена.
Следовательно, сборщик мусора Python работает автоматически, и программистам не нужно беспокоиться об этом, в отличие от C.
CMS (Параллельная пометка и зачистка) GC
Также известен как параллельный сборщик низких пауз. Для малой сборки мусора задействуются несколько потоков, и происходит это через такой же алгоритм, как в параллельном сборщике. Основная сборка мусора многопоточна, как и в старом параллельном GC, но CMS работает одновременно с процессами приложений, чтобы свести к минимуму события “остановки мира”.
Из-за этого сборщик CMS потребляет больше ресурсов процессора, чем другие сборщики. Если у вас есть возможность выделить больше ЦП для повышения производительности, то CMS предпочтительнее, чем простой параллельный сборщик. В CMS GC не выполняется уплотнение.
Аргумент JVM для использования параллельного сборщика мусора с разверткой меток: .
Общие сведения о управление памятью
Управление памятью — это процесс размещения новых объектов и удаление неиспользуемых объектов, чтобы освободить место для этих новых ассигнований объектов. Традиционным для языков программирование способом управления памятью является ручной. Его сущность является в следующем:
- Для создания объекта в динамической памяти программист явно вызывает команду выделения памяти. Эта команда возвращает указатель на выделенную область памяти, который сохраняется и используется для доступа к ней.
- До тех пор, пока созданный объект нужен для работы программы, программа обращается к нему через ранее сохранённый указатель.
- Когда надобность в объекте проходит, программист явно вызывает команду освобождения памяти, передавая ей указатель на удаляемый объект.
- Ручное управление памятью допускает потенциально возможные две проблемы: висячие ссылки и утечки памяти.
Висячая ссылка — это оставшаяся в использовании ссылка на объект, который уже удалён. После удаления объекта все сохранившиеся в программе ссылки на него становятся «висячими». Память, занимаемая ранее объектом, может быть передана операционной системе и стать недоступной, или быть использована для размещения нового объекта в той же программе.
Утечка памяти — процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих ненужные уже участки памяти.
Все проблемы ручного способа управления памяти в Java решает автоматический сборщик мусора. Но пред ознакомлением со сборщиком мусора, нужно разъяснить понятие кучи(heap).
Program order
Program order (лучше не переводить, чтобы не возникло путаницы) — общий порядок потока, выполняющего действия, который отражает порядок, в котором должны быть выполнены все действия с соответствии с семантикой intra-thread semantic потока.
Действия называются sequentially consistent (лучше тоже не переводить), если все действия выполняются в общем порядке, который соответствует program order, а также каждое чтение переменной видит последнее значение, записанное туда до этого в соответствии с порядком выполнения.
Если в программе нет состояния гонки, то все запуски программы будут sequentially consistent.
Пространство кучи в Java
Пространство кучи в Java используется для динамического выделения памяти для объектов Java и классов JRE во время выполнения . Новые объекты всегда создаются в пространстве кучи, а ссылки на эти объекты хранятся в памяти стека.
Эти объекты имеют глобальный доступ и могут быть доступны из любой точки приложения.
Эта модель памяти далее разбивается на более мелкие части, называемые поколениями, это:
- Молодое поколение – именно здесь выделяются и стареют все новые объекты. Незначительная сборка мусора происходит, когда это заполняется
- Старое или арендованное поколение – здесь хранятся давно сохранившиеся объекты. Когда объекты хранятся в Молодом поколении, устанавливается пороговое значение для возраста объекта, и когда это пороговое значение достигнуто, объект перемещается в старое поколение
- Постоянная генерация – это состоит из метаданных JVM для классов среды выполнения и методов приложения
Эти различные части также обсуждаются в этой статье – Разница между JVM, JRE и JDK.
Мы всегда можем манипулировать размером памяти кучи в соответствии с нашими требованиями. Для получения дополнительной информации посетите эту связанную статью Baeldung .
3.1. Основные характеристики кучной памяти Java
Помимо того, что мы обсуждали до сих пор, ниже приведены некоторые другие особенности пространства кучи:
- Доступ к нему осуществляется с помощью сложных методов управления памятью, которые включают в себя Молодое поколение, Старое или Штатное поколение и Постоянное поколение
- Если пространство кучи заполнено, Java выдает java.lang.OutOfMemoryError
- Доступ к этой памяти относительно медленнее, чем к стековой памяти
- Эта память, в отличие от стека, не освобождается автоматически. Ему нужен сборщик мусора, чтобы освободить неиспользуемые объекты, чтобы сохранить эффективность использования памяти
- В отличие от стека, куча не является потокобезопасной и должна быть защищена путем правильной синхронизации кода
Подсчет ссылок в Python
Подсчет ссылок показывает, сколько раз другие объекты ссылаются на данный объект. Когда назначается ссылка на объект, счетчик объектов увеличивается на единицу. Когда ссылки на объект удаляются, количество объектов уменьшается. Когда счетчик ссылок становится равным нулю, диспетчер памяти Python выполняет освобождение. Давайте сделаем это простым для понимания.
Пример:
Предположим, есть две или более переменных, которые содержат одно и то же значение, поэтому виртуальная машина Python скорее создает другой объект с таким же значением в частной куче. Фактически это указывает на то, что вторая переменная указывает на изначально существующее значение в частной куче.
Это очень полезно для сохранения памяти, которая может использоваться другой переменной.
x = 20
Когда мы присваиваем значение переменной x, целочисленный объект 10 создается в памяти кучи, и его ссылка присваивается x.
x = 20 y = x if id(x) == id(y): print("The variables x and y are referring to the same object")
В приведенном выше коде мы присвоили y = x, что означает, что объект y будет ссылаться на тот же объект, потому что Python выделил ту же ссылку на объект новой переменной, если объект уже существует с тем же значением.
Теперь посмотрим на другой пример.
x = 20 y = x x += 1 If id(x) == id(y): print("x and y do not refer to the same object")
Выход:
x and y do not refer to the same object
Переменные x и y не ссылаются на один и тот же объект, потому что x увеличивается на единицу, x создает новый объект ссылки, а y по-прежнему ссылается на 10.
Внешняя фрагментация
При внешней фрагментации у нас есть свободный блок памяти, но мы не можем назначить его процессу, потому что блоки не являются смежными.
И первая, и самая подходящая системы для распределения памяти, подверженной внешней фрагментации. Для преодоления проблемы внешней фрагментации используется уплотнение. В технике уплотнения все свободное пространство памяти объединяется и образует один большой блок. Таким образом, это пространство может быть эффективно использовано другими процессами.
Другое возможное решение внешней фрагментации — позволить логическому адресному пространству процессов быть несмежным, что позволяет процессу выделять физическую память там, где последняя доступна.
Paging:
Paging — это схема управления памятью, которая устраняет необходимость непрерывного выделения физической памяти. Эта схема позволяет физическому адресному пространству процесса быть несмежным.
- Логический адрес или виртуальный адрес (представлен в битах): адрес, генерируемый ЦП.
- Логическое адресное пространство или виртуальное адресное пространство (представленное словами или байтами): набор всех логических адресов, сгенерированных программой.
- Физический адрес (представлен в битах): адрес, фактически доступный в блоке памяти.
- Физическое адресное пространство (выраженное словами или байтами): набор всех физических адресов, соответствующих логическим адресам.
Пример:
- Если логический адрес = 31 бит, то логическое адресное пространство = 2 31слово = 2 G слов (1 G = 2 30 )
- Если логическое адресное пространство = 128 M слов = 2 7* 2 20 слов, то логический адрес = log 2 2 27 = 27 бит
- Если физический адрес = 22 бита, то физическое адресное пространство = 2 22слова = 4 M слов (1 M = 2 20 )
- Если физическое адресное пространство = 16 M слов = 2 4* 2 20 слов, то физический адрес = log 2 2 24 = 24 бита.
Преобразование виртуального адреса в физический выполняется блоком управления памятью (MMU), который является аппаратным устройством, и это преобразование известно как метод подкачки.
- Физическое адресное пространство концептуально разделено на несколько блоков фиксированного размера, называемых кадрами.
- Логическое адресное пространство также разделено на блоки фиксированного размера, называемые страницами.
- Размер страницы = Размер кадра
Рассмотрим пример:
- Физический адрес = 12 бит, тогда физическое адресное пространство = 4 К слов
- Логический адрес = 13 бит, затем логическое адресное пространство = 8 К слов
- Размер страницы = размер кадра = 1 тыс. Слов (предположение)
Адрес, генерируемый ЦП, делится на
- Номер страницы (p):количество битов, необходимых для представления страниц в логическом адресном пространстве или номер страницы.
- Смещение страницы (d):количество битов, необходимых для представления определенного слова на странице или размер страницы логического адресного пространства, или номер слова страницы или смещение страницы.
Физический адрес делится на
- Номер кадра (f):количество битов, необходимых для представления кадра физического адресного пространства или кадра номера кадра.
- Смещение кадра (d):количество битов, необходимых для представления конкретного слова в кадре, или размер кадра в физическом адресном пространстве, или номер слова кадра, или смещение кадра.
Аппаратная реализация таблицы страниц может быть выполнена с использованием выделенных регистров. Но использование регистра для таблицы страниц является удовлетворительным только в том случае, если таблица страниц мала. Если таблица страниц содержит большое количество записей, мы можем использовать TLB (буфер просмотра трансляции), специальный небольшой аппаратный кеш для быстрого просмотра.
- TLB — это ассоциативная высокоскоростная память.
- Каждая запись в TLB состоит из двух частей: тега и значения.
- Когда эта память используется, то элемент сравнивается со всеми тегами одновременно. Если элемент найден, то соответствующее значение возвращается.
Время доступа к основной памяти = м
Если таблица страниц хранится в основной памяти,
Эффективное время доступа = m (для таблицы страниц) + m (для конкретной страницы в таблице страниц)
Стековая память в Java
Стековая память в Java используется для статического выделения памяти и выполнения потока. Он содержит примитивные значения, специфичные для метода, и ссылки на объекты, находящиеся в куче, на которые ссылаются из метода.
Доступ к этой памяти осуществляется в порядке “Последний вход-первый выход” (LIFO). Всякий раз, когда вызывается новый метод, создается новый блок в верхней части стека, содержащий значения, характерные для этого метода, такие как примитивные переменные и ссылки на объекты.
Когда метод завершает выполнение, соответствующий кадр стека сбрасывается, поток возвращается к вызывающему методу, и пространство становится доступным для следующего метода.
2.1. Основные характеристики стековой памяти
Помимо того, что мы обсуждали до сих пор, ниже приведены некоторые другие особенности стековой памяти:
- Он растет и сжимается по мере вызова и возврата новых методов соответственно
- Переменные внутри стека существуют только до тех пор, пока работает метод, который их создал
- Он автоматически выделяется и освобождается, когда метод завершает выполнение
- Если эта память заполнена, Java выдает java.lang.StackOverflowError
- Доступ к этой памяти осуществляется быстро по сравнению с памятью кучи
- Эта память является потокобезопасной, так как каждый поток работает в своем собственном стеке
G1 (Мусор — первым) GC
G1GC был задуман как замена CMS и разрабатывался для многопоточных приложений, которые характеризуются крупным размером кучи (более 4 ГБ). Он параллелен и конкурентен, как CMS, но “под капотом” работает совершенно иначе, чем старые сборщики мусора.
Хотя G1 также действует по принципу поколений, в нем нет отдельных пространств для молодого и старшего поколений. Вместо этого каждое поколение представляет собой набор областей, что позволяет гибко изменять размер молодого поколения.
G1 разбивает кучу на набор областей одинакового размера (от 1 МБ до 32 МБ — в зависимости от размера кучи) и сканирует их в несколько потоков. Область во время выполнения программы может неоднократно становиться как старой, так и молодой.
После завершения этапа разметки G1 знает, в каких областях содержится больше всего мусора. Если пользователь заинтересован в минимизации пауз, G1 может выбрать только несколько областей
Если время паузы неважно для пользователя или предел этого времени установлен высокий, G1 пройдет по большему числу областей
Поскольку G1 GC идентифицирует регионы с наибольшим количеством мусора и сначала выполняет сбор мусора в них, он и называется: “Мусор — первым”.
Помимо областей Эдема, Выживших и Старой памяти, в G1GC присутствуют еще два типа.
- Humongous (Огромная) — для объектов большого размера (более 50% размера кучи).
- Available (Доступная) — неиспользуемое или не выделенное пространство.
Аргумент JVM для использования сборщика мусора G1: .
Оператор new может завершиться со сбоем
При запросе памяти у операционной системы в редких случаях у нее может не быть памяти, чтобы удовлетворить запрос.
По умолчанию, если не срабатывает, выдается исключение . Если это исключение не обрабатывается должным образом (а этого не произойдет, поскольку мы еще не рассмотрели исключения или их обработку), программа просто аварийно завершится с необработанной ошибкой исключения.
Во многих случаях создание исключения (или сбой программы) нежелательно, поэтому существует альтернативная форма , которая может использоваться, чтобы сообщить о возврате нулевого указателя, если память не может быть выделена. Это делается путем добавления константы между ключевым словом и типом распределения:
В приведенном выше примере, если не может выделить память, он вернет нулевой указатель вместо адреса выделенной памяти.
Обратите внимание, что если вы затем попытаетесь выполнить косвенное обращение через этот указатель, это приведет к неопределенному поведению (скорее всего, ваша программа завершится со сбоем). Следовательно, перед использованием выделенной памяти рекомендуется проверять все запросы к памяти, чтобы убедиться, что они действительно выполнены
Поскольку запрос новой памяти редко дает сбой (и почти никогда в среде разработки), эту проверку часто забывают делать!
Что такое Утечка памяти
Утечка памяти-это ситуация , когда в куче присутствуют объекты, которые больше не используются, но сборщик мусора не может удалить их из памяти и, таким образом, они излишне поддерживаются.
Утечка памяти-это плохо, потому что она блокирует ресурсы памяти и со временем снижает производительность системы . И если с этим не разобраться, приложение в конечном итоге исчерпает свои ресурсы, окончательно завершившись фатальным java.lang.OutOfMemoryError .
Существует два различных типа объектов, которые находятся в памяти кучи — со ссылками и без ссылок. Ссылочные объекты-это те, у которых все еще есть активные ссылки в приложении, в то время как у объектов без ссылок нет активных ссылок.
Сборщик мусора периодически удаляет объекты без ссылок, но никогда не собирает объекты, на которые все еще ссылаются. Именно здесь могут произойти утечки памяти:
Симптомы утечки памяти
- Серьезное снижение производительности при длительной непрерывной работе приложения
- OutOfMemoryError ошибка кучи в приложении
- Спонтанные и странные сбои приложений
- В приложении иногда заканчиваются объекты подключения
Давайте подробнее рассмотрим некоторые из этих сценариев и то, как с ними бороться.
Интерпретатор Python по умолчанию ↑
Интерпретатор Python по умолчанию, CPython, фактически написан на языке программирования C.
Когда я впервые это услышал, то поразился. Язык, которой написан на другом языке?! Ну, не совсем так, но вроде как.
Язык Python определен в справочном руководстве, написанном на английском языке. Однако само по себе это руководство не так уж и полезно. Вам все еще что-то нужно для интерпретации написанного кода на основе этих правил в руководстве.
Вам также что-то нужно для реального выполнения интерпретируемого кода на компьютере. Интерпретатор Python по умолчанию удовлетворяет обоим этим требованиям. Он преобразует ваш код Python в инструкции, которые затем запускаются на виртуальной машине.
Python — это интерпретируемый язык программирования. Ваш код Python фактически компилируется в более машиночитаемые инструкции, называемые . Эти инструкции интерпретируются виртуальной машиной при запуске кода.
Вы когда-нибудь видели файл .pyc или папку __pycache__? Это байт‑код, который интерпретируется виртуальной машиной.
Важно отметить, что существуют реализации, отличные от CPython. IronPython компилируется для работы в Microsoft Common Language Runtime
Jython компилируется в байт‑код Java для работы на виртуальной машине Java. Затем есть PyPy, но он заслуживает отдельной статьи, поэтому я просто упомяну его вскользь. В этом уроке я сосредоточусь на управлении памятью, осуществляемом стандартной реализацией Python, CPython.
Итак, CPython написан на C и интерпретирует байт‑код Python. При чем здесь управление памятью? Что ж, алгоритмы и структуры управления памятью существуют в коде CPython на C. Чтобы понять управление памятью в Python, вы должны получить базовое представление о самом CPython.
CPython написан на C, который изначально не поддерживает объектно-ориентированное программирование. Из-за этого в коде CPython есть довольно много интересных дизайнов.
Возможно, вы слышали, что все в Python является объектом, даже такие типы, как и . Что ж, это верно на уровне интерпретатора CPython. Есть структура под названием , которая использует любой другой объект в CPython.
, прародитель всех объектов в Python, содержит только две вещи:
- — счетчик ссылок
- — указатель на другой тип
Счетчик ссылок используется для сборки мусора (garbage collection). Тогда у вас есть указатель на фактический тип объекта. Этот тип объекта — просто еще одна структура, описывающая объект Python (например, или ).Каждый объект имеет свой собственный объектно-зависимый распределитель памяти, который знает, как получить память для хранения этого объекта. У каждого объекта также есть объектно-зависимый механизм освобождения памяти, который «освобождает» память, когда она больше не нужна.
Однако во всех этих разговорах о выделении и освобождении памяти есть важный фактор. Память — это общий ресурс на компьютере, и могут случиться неприятности, если два разных процесса попытаются записать в одно и то же место в одно и то же время.
Куча
В Java все объекты находятся в области памяти под названием куча. Куча создается, когда JVM запускается и может увеличиваться или уменьшаться в размерах во время выполнения приложения. Когда куча становится полной, происходит механизм сборки мусора. Все объекты, которые никогда больше не будут использоватся, очищаются. тем самым освобождая место для новых объектов.
Также нужно обратить внимание, что JVM использует больше памяти, чем занимает куча. Например, для методов Java и стеков потоков выделяется память отдельно от кучи
Размер кучи зависит от используемой платформы, но, как правило, это где-то между 2 и 128 Кб.
Что такое оперативная память?
Сначала быстрый учебник. ОЗУ означает оперативную память и обеспечивает хранение текущих задач и процессов. Разница между оперативной памятью и остальным хранилищем на вашем Mac заключается в том, что она быстрее, поэтому ваш Mac предназначен для хранения определенных вещей в оперативной памяти, чтобы ускорить процесс.
Большинство Mac поставляются с 8 ГБ ОЗУ, хотя у некоторых более старых моделей только 4 ГБ. Этого может быть достаточно, если вы не запускаете приложения и игры, требующие большого объема памяти, но даже самый обычный пользователь может столкнуться с проблемами ОЗУ из-за процессов сбоев памяти, связанных с плохо разработанными веб-страницами и приложениями.
Если ваш Mac использует большую часть доступной оперативной памяти, у вас могут возникнуть такие проблемы, как:
Признаки того, что вам не хватает оперативной памяти
- Спиннинг пляжный мяч
- Сообщение «Ваша система исчерпала память приложения»
- Принимая возраста, чтобы загрузить
- Сбой приложений
К сожалению, сложно обновить ОЗУ на Mac, как вы можете видеть из этой статьи о том, как обновить ОЗУ на Mac. В некоторых случаях обновление ОЗУ может быть выполнено, но, вероятно, это решение не для всех, и мы рекомендуем вам попробовать некоторые из приведенных ниже советов, прежде чем спешить и покупать больше ОЗУ.
Другим вариантом является загрузка стороннего приложения, которое обещает оптимизировать вашу оперативную память — мы рассмотрим некоторые из таких программ ниже. Тем не менее, следует отметить, что, в общем, MacOS способна эффективно управлять памятью и обрабатывать журналы и наличные и тому подобное, поэтому вам не нужно действительно стороннее приложение, чтобы сделать это за вас. Однако, если это частая проблема, с которой вы сталкиваетесь, возможно, стоит рассмотреть эти варианты.
Перед установкой дополнительной оперативной памяти или загрузкой приложения вы можете сделать несколько вещей, которые могут освободить вашу оперативную память и решить проблемы с памятью на вашем Mac.
Необходимость динамического распределения памяти
C++ поддерживает три основных типа распределения памяти, два из которых вы уже видели.
- Статическое распределение памяти выполняется для статических и глобальных переменных. Память для этих типов переменных выделяется один раз при запуске вашей программы и сохраняется на протяжении всего ее времени жизни.
- Автоматическое распределение памяти выполняется для параметров функций и локальных переменных. Память для этих типов переменных выделяется при входе в соответствующий блок и освобождается при выходе из блока столько раз, сколько необходимо.
- Динамическое распределение памяти – тема этой статьи.
И статическое, и автоматическое распределение имеют две общие черты:
- Размер переменной/массива должен быть известен во время компиляции.
- Выделение и освобождение памяти происходит автоматически (при создании/уничтожении переменной).
В большинстве случаев это нормально. Однако вы столкнетесь с ситуациями, когда одно или оба этих ограничения вызывают проблемы, обычно при работе с внешними (пользовательскими или файловыми) входными данными.
Например, мы можем захотеть использовать строку для хранения чьего-либо имени, но мы не знаем, какой длины это имя, пока оно не будет введено. Или мы можем захотеть прочитать несколько записей с диска, но мы не знаем заранее, сколько там записей. Или мы можем создавать игру с переменным количеством пытающихся убить игрока монстров (которое меняется со временем, когда некоторые монстры умирают и появляются новые).
Если нам нужно объявить размер всего во время компиляции, лучшее, что мы можем сделать, это попытаться угадать максимальный размер переменных, которые нам понадобятся, и надеяться, что этого будет достаточно:
Это плохое решение как минимум по четырем причинам:
Во-первых, это приводит к потере памяти, если переменные на самом деле не используются. Например, если мы выделяем 25 символов для каждого имени, но в среднем имена имеют длину всего 12 символов, мы используем вдвое больше, чем нам действительно нужно. Или рассмотрите приведенный выше массив рендеринга: если рендеринг использует только 10 000 полигонов, у нас 20 000 полигонов памяти не используются!
Во-вторых, как узнать, какие биты памяти действительно используются? Для строк это просто: строка, начинающаяся с \0, явно не используется. А как насчет ? Он сейчас жив или мертв? Это требует какого-то способа отличать активные элементы от неактивных, что увеличивает сложность и может использовать дополнительную память.
В-третьих, большинство обычных переменных (включая фиксированные массивы) размещаются в части памяти, называемой стеком. Объем стековой памяти для программы, как правило, довольно невелик – Visual Studio по умолчанию устанавливает размер стека равным 1 МБ. Если вы превысите это число, произойдет переполнение стека, и операционная система, вероятно, закроет программу.
В Visual Studio вы можете увидеть это при запуске следующей программы:
Ограничение всего 1 МБ памяти было бы проблематичным для многих программ, особенно тех, которые имеют дело с графикой.
В-четвертых, что наиболее важно, это может привести к искусственным ограничениям и/или переполнению массива. Что происходит, когда пользователь пытается прочитать 600 записей с диска, но мы выделили память максимум для 500 записей? Либо мы должны выдать пользователю ошибку, прочитав только 500 записей, либо (в худшем случае, когда мы вообще не обрабатываем этот случай) переполнить массив записей и наблюдать, как происходит что-то плохое
К счастью, эти проблемы легко решаются с помощью динамического распределения памяти. Динамическое распределение памяти – это способ работающих программ при необходимости запрашивать память у операционной системы. Эта память не поступает из ограниченной стековой памяти программы – вместо этого она выделяется из гораздо большего пула памяти, управляемого операционной системой, называемого кучей. На современных машинах размер кучи может составлять гигабайты.
Нулевые указатели и динамическое выделение памяти
Нулевые указатели (указатели со значением или ) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:
// Если для ptr до сих пор не выделено памяти, то выделяем её
if (!ptr)
ptr = new int;
1 |
// Если для ptr до сих пор не выделено памяти, то выделяем её if(!ptr) ptr=newint; |
Удаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:
if (ptr)
delete ptr;
1 |
if(ptr) delete ptr; |
Вместо этого вы можете просто написать:
delete ptr;
1 | delete ptr; |
Если не является нулевым, то динамически выделенная переменная будет удалена. Если значением указателя является нуль, то ничего не произойдет.
Synchronization order
Synchronization order (порядок синхронизации, но лучше не переводить) — общий порядок всех действий по синхронизации в выполнении программы.
Действия по синхронизации вводят связь synchronized-with (синхронизировано с):
- Действие освобождения блокировки монитора synchronizes-with все последующие действия по взятию блокировки этого монитора.
- Присвоение значения
volatile переменной synchronizes-with все последующие чтения этой переменной любым потоком. - Действие запуска потока synchronizes-with с первым действием внутри запущенного потока.
- Присвоение значения по умолчанию (0, false, null) каждой переменной synchronizes-with с первым действием каждого потока.
- Последнее действие в потоке synchronizes-with с любым действием других потоков, которые .
- Если поток 1 , то прерывание выполнения потока 2 synchronizes-with с любой точкой, где другой поток (и прерывающий тоже) проверяет, что поток 2 был прерван (
InterruptedException,
Thread.interrupted,
Thread.isInterrupted).