В этой статье — важное понятие из компьютерной теории. Читайте, если хотите разбираться в устройстве компьютеров и памяти. Особенно полезно для бэкенда и разработки высоконагруженных систем.
Ситуация
Когда мы пишем программы, мы обычно действуем по такому принципу:
- если надо — объявляем переменную и храним в ней данные;
- если переменных нужно много — делаем много переменных;
- переменные можно объявлять внутри циклов, а циклы вкладывать в другие циклы;
В итоге наши программы могут обрабатывать сотни, тысячи и миллионы переменных с числами и текстом, и для нас это в порядке вещей. Компьютер, собственно, и создан для того, чтобы обрабатывать за нас эти массивы данных.
Проблема
Когда мы объявляем новую переменную, под неё выделяется кусок памяти, где она будет храниться. Часто бывает такое, что даже если эта переменная потом нигде не используется, то она всё равно занимает место в памяти.
Если таких переменных будет много, то программа может занять много памяти. Когда память забивается, компьютер начнёт тормозить. Вся занятая память освободится только тогда, когда программу закроют, а до того времени всё будет тормозить.
ПЗУ и ОЗУ в майнкрафте. — Atlas
Особенно это опасно на контроллерах или носимых устройствах: там обычно мало памяти и её легко заполнить. А контроллеры управляют не только умным домом, но и, например, самолётами или промышленными станками. Если не подумать о памяти в контроллере двигателя самолёта, то у тебя на третьем часу полёта откажет двигатель.
Решение — очистка мусора и управление памятью
Кто-то в программе должен озаботиться тем, чтобы ненужные переменные умирали и высвобождали занятую ранее память. В этом деле есть два подхода: ручной и автоматический.
В ручном режиме программист сам следит за каждой переменной, объектом и сущностью. Когда объект или переменная больше не нужны, он прямо в коде пишет: «Этот готов, уносите». Для этого он использует специальные команды-деструкторы, которые удаляют переменную и освобождают память. Теперь эту область памяти может взять себе другая программа или переменная, тогда ресурсы расходуются максимально экономно.
Автоматический режим называется сборкой мусора. Это такая отдельная мини-программа внутри основной программы, которая периодически пробегает по объектам и переменным в коде и смотрит, нужны они или нет. Если нет — сборщик мусора сам удаляет переменную и освобождает память.
Особенности ручного управления
При ручной работе с памятью программист получает полный контроль над ресурсами и может в любой момент освободить уже ненужную память. Это значит, что можно написать такую программу, когда в сумме переменным нужно 500 мегабайт памяти, но за счёт своевременного удаления всегда заняты только 100 мегабайт.
Ручное управление идеально для систем со слабыми ресурсами и систем реального времени — там, где программа не имеет права тормозить.
Вместе с тем такой подход требует высокой квалификации программиста. Нужно точно знать, какая переменная тебе нужна и когда; как устроены циклы твоей программы; как работает процессор и куда он может посмотреть в тот или иной момент.
Что делать если лагает майнкрафт? Ответ здесь!
Особенности автоматического сборщика
Автоматический сборщик сам ходит по программе во время исполнения и аккуратно подчищает память, как только находит мусор.
Вроде хорошо, но нет. Сборщик мусора тоже работает неидеально:
❌ Сборщик удаляет только те переменные, в которых он уверен стопроцентно. Если есть один шанс, что переменная может когда-нибудь понадобиться, — её оставляют в памяти. То есть эффективность не стопроцентная.
❌ Сборщик — это отдельная программа, которая работает вместе с основной. И ей тоже нужны ресурсы и процессорное время. Это замедляет работу основной программы. Как если бы уборщик приходил в отделение Сбербанка посреди рабочего дня и заставлял всех на два часа покинуть рабочие места, чтобы он тут провёл влажную уборку.
❌ Если рабочей памяти очень мало, то сборщик будет работать постоянно. Но ему тоже нужна своя память для работы. И может получиться, что сборщик, например, занимает 100 МБ, а освобождает 10 МБ. Может оказаться так, что без сборщика программа будет работать эффективнее.
Автоматические сборщики — добро или зло?
Тут у разработчиков мнения разделяются.
С точки зрения быстродействия сборщики — однозначно зло, потому что они всегда замедляют работу. Есть ситуации, когда замедление незаметно, а бывает, когда оно критично.
- Например, если офисное приложение иногда подвисает на полсекунды, это почти никто не замечает. Возможно, оно у вас подвисает прямо сейчас.
- А если на полсекунды подвиснет контроллер ракетного двигателя, то эта ракета полетит не в космос, а в Вашингтон. Хьюстон, у нас проблема.
С точки зрения удобства сборщики — однозначно добро. Пишешь полезный код, а умная машина сама за тобой прибирается. Программы выходят быстрее, для их поддержки нужно меньше людей, которые могут быть менее компетентными.
Что выбрать?
С выбором интересная ситуация.
Если вы пишете приложения для iOS или OSX, вам нельзя использовать сборщики мусора из соображений быстродействия.
Языки типа JavaScript и Ruby собирают мусор сами, вы об этом можете даже никогда не узнать.
В некоторые языки можно подключить сбор мусора, пожертвовав производительностью — например, в Java, C или C++.
Путь самурая — вручную управлять памятью и делать суперпроизводительные приложения, которые летают даже на процессоре от карманного калькулятора. Но жизнь сложна и разнообразна, и однажды за всеми нами тоже придут наши собственные… сборщики.
Веб-разработка — это новый чёрный
На базе веб-технологий делают всё — от сложного софта до высокобюджетных игр. Изучите технологии и начните карьеру в ИТ. Старт бесплатно. Попробуйте, вдруг вам понравится.
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Еще по теме
Автоматическое оглавление на странице
Поручите это машине.
116 тысяч рублей в месяц — средняя зарплата для фронтенда. Как им стать
Простая инструкция для начинающих.
JavaScript для новичков: чем опасны нестрогие типы данных
В JavaScript есть удобная штука, которая может сильно вам навредить.
3D-аватары: как это работает
Разбираем на примере блогера CodeMiko.
Ну что ж, переходим на Linux. С чего начать?
Краткое пособие по импортозамещению
Что такое API
Это как нанять внешнего сотрудника на удалёнку.
Что такое AJAX
Как добавить что-то новое на страницу без перезагрузки
Что такое PHP и почему его все ненавидят
Несмотря на то, что 83% сайтов в интернете работают на PHP.
Вам мало языка C? Попробуйте C++
Шустрый, мощный, весь обвешан классами.
Источник: thecode.media
Фризит майнкрафт при резком сбросе озу
Когда играю в Майн, зависает игра резко на пару секунд, заметил что проблема связанна с тем, когда озу накапливается очень много, майн её сбрасывает и в этот момент происходит фриз на пару секунд, раньше была такая же проблема, но решалась уменьшение количества озу для майна, сейчас это не помогает
Александр Иванов
1 год назад
Действительно, такая проблема есть. Заключается она в срабатывании так называемого «сборщика мусора». Чтобы понять, что это такое, и как бороться с этой проблемой, надо разобраться с тем, как работает сборщик.
Чтобы процессору обработать какие-то данные, их сначала надо загрузить в оперативную память. В процессе вычислений процессор по частям подгружает эти данные в свой кэш, а затем возвращает результат в ту же оперативную память. При этом, старые данные никуда не деваются и продолжают храниться в ОЗУ. Отсюда появляется рост занимаемой игрой памяти с течением времени, который можно наблюдать в меню отладки, или более наглядно при помощи мода https://modrinth.com/mod/memory-usage-screen.
Когда количество занятой оперативной памяти достигает определённого предела, срабатывает сборщик мусора. Это алгоритм, который отличает старые хранящиеся в ОЗУ отработавшие данные от необходимых программе в данный момент и производит их удаление. Этот процесс всегда характеризуется полной остановкой всех вычислений, из-за чего игра и зависает.
Интересно, что чем меньше выделен объём ОЗУ, тем быстрее он забивается остатками вычислений (входными данными для процессора), однако при низких значениях выделенной памяти ͟п͟р͟о͟л͟а͟г͟а͟ ͟н͟е͟ ͟п͟р͟о͟и͟с͟х͟о͟д͟и͟т.
Почему же? Дело в том, что чем меньше ОЗУ выделено для JVM, тем быстрее сборщик мусора его обрабатывает. То есть, сборщик мусора работает ͟ч͟а͟щ͟е͟,͟ ͟н͟о͟ ͟б͟ы͟с͟т͟р͟е͟е. При больших же значениях выделенной памяти сборщик мусора обрабатывает весь объём ОЗУ редко, но долго, создавая характерное зависание.
Именно поэтому нужно соблюсти идеальный баланс между выделенной ОЗУ и временем его чистки. Но важно помнить, что когда ОЗУ выделено настолько мало, что Майнкрафт не может прассчитать тик сервера без очистки мусора, игра начинает сильно виснуть и вылетает.
͟Ч͟т͟о͟ ͟с͟ ͟э͟т͟и͟м͟ ͟д͟е͟л͟а͟т͟ь͟?
Путей решения несколько:
• Покупка более быстрой оперативки
Чем быстрее скорость чтения-записи, тем быстрее программа и сборщик мусора может с ней взаимодействовать.
• Баланс между объёмом выделенной ОЗУ и временем зависаний
Просто попробуйте уменьшить количество выделенной памяти.
• Запуск игры с Java 16
В новых версиях Java сборщики мусора работают гораздо быстрее, поэтому зачастую достаточно просто.
• Подбор более эффективного сборщика мусора
Это оптимальный вариант, так как алгоритмов сборки мусора существует несколько. Вот самые популярные:
▶ G1 — Стандартный сборщик мусора, очищает память довольно часто, но лишь небольшими порциями. Это позволяет игре не зависать, однако при быстром накоплении мусора G1 может не справиться.
▶ Shenandoah — Эффективно очищает всю память при заполнении ≈80% выделенного объёма, не вызывая пролаги. Сам пользуюсь им.
▶ ConcMarkSweep — Использует многопоточный алгоритм очистки, благодаря чему скорость чистки оперативной памяти кратно повышается.
Настройка запуска игры с определённым сборщиком мусора довольно проста — нужно лишь указать его в параметрах запуска JVM (примеры параметров JVM в комментариях). В официальном лаунчере это делается здесь:
А TLauncher Legacy и так по умолчанию использует ConcMarkSweepGC, нужно просто удостовериться, что в настройках стоит эта галочка:
Источник: minecraftru.net
Сборщик мусора памяти майнкрафт
После завершения вызова метода isGreetingTooHard локальная переменная msg будет уничтожена и в нашей программе не останется ни одной ссылки на созданный объект строки. Значит, программа физически не сможет использовать объект нигде после вызова метода и GC может спокойно удалить его из памяти, не опасаясь что обращения нему в будущем.
В программах часто объекты путешествуют из одних методов в другие, сохраняются и удаляются из полей, массивов, коллекций, что усложняет определение факта потери на них ссылок. Но для GC это не является неразрешимой проблемой и он способен определить, можно ли до объекта дойти по ссылкам из существующей в этот момент ячейки программы (переменной, параметра. ), и, если нельзя, то этот объект точно является мусором, ведь программа его физически уже никогда не сможет использовать.
Когда мусор не найдётся?
Объект может быть достижим из живых локальных переменных / статических полей и других активных ячеек программы, но при этом всё равно быть уже ненужным. Этот объект будет мусором, но GC не будет очищать память от него. Рассмотрим некоторые примеры подобных и других утечек памяти в Java.
Больше полезной информации вы найдете на нашем телеграм-канале «Библиотека джависта».
Статические поля
В отличие от нестатических полей, которые существуют пока не удалён содержащий их объект, статические поля обычно живут вплоть до завершения программы. Если в статическом поле сохранена ссылка на объект, то этот объект всегда будет считаться достижимым из программы и GC удалять его не будет. Если этот объект больше не нужен, то поле следует об-null-ить самому, чтобы GC смог его очистить.
Незакрытые ресурсы
Под открытые ресурсы (например, соединения или потоки ввода-вывода) выделяется память, которая освобождается при закрытии через вызов метода close . Уже ненужные программе но незакрытые ресурсы могут блокировать освобождение занятой памяти. Частая причина этого вида утечки это ошибка в программе, из-за которой ресурс не закрывается, но при этом ссылка на объект ресурса теряется. Например, в следующем примере сканнер закрыт не будет, если из входного потока будет считан 0, на который попытаются разделить:
public static double divide() < Scanner scanner = new Scanner(. ); double result = 1; while (scanner.hasNextInt()) < result /= scanner.nextInt(); >scanner.close(); return result; >
Чтобы избежать этого рода утечки памяти, используйте try-with-resources для автозакрытия ресурсов даже в случае возникновения исключений (до Java 7 используйте для этих целей finally ):
public static double divide() < try (Scanner scanner = new Scanner(. )) < double result = 1; while (scanner.hasNextInt()) < result /= scanner.nextInt(); >return result; > >
Внутренние классы
Ряд механизмов в Java предполагают наличие неявной ссылки на объект. К числу таких относится и внутренний класс. Рассмотрим пример:
public class Country < //Население страны protected String[] population = new String[1_000_000_000]; protected String headOfState = «Mr. President»; public ForeignMinister newForeignMinister() < return new ForeignMinister(); >public class ForeignMinister < protected int deals = 0; // количество успешных сделок public void acceptInvitation() < deals++; System.out.println(headOfState + » приглашён на глобальную встречу»); >> >
Класс описывает объект с информацией о стране – в полях содержатся имя главы государства (поле headOfState ) и имена всех жителей (большой массив population ). Внутренний класс описывает объект министра иностранных дел – поле количества успешных договоров и метод приглашения главы государства на саммит.
Теперь представим себе метод, который создаёт (и никуда не сохраняет) объект страны, спрашивает у него объект министра иностранных дел и возвращает только министра из метода:
public static ForeignMinister getAnyMinister()
Будет ошибкой заключить, что созданный объект страны будет подчищен GC после завершения метода. Класс министра это внутренний классом, его объекту доступны все поля объекта страны, от которой он создан. Для этого джава будет поддерживать неявную ссылку в объекте министра на объект страны, храня в памяти его вместе со всеми полями, включая огромный массив с именами жителей, который нашей программе логически не нужен, а значит является мусором.
Чтобы избежать подобной утечки памяти, достаточно помнить об особенностях работы внутренних классов. Если критично, можно всегда использовать вместо них статические вложенные классы, у которых такого эффекта нет.
Собственные структуры данных
Утечка памяти это частая ошибка при проектировании своих собственных структур данных. Представим себе, что мы решили написать собственную реализацию стека, т.е. набор данных с двумя операциями: push(значение) вставляет новое значение в конец набора; pop() вынимает значение с конца набора.
Рассмотрим упрощённую реализацию без красивой обработки ошибок:
public class LeakyStack < private T[] data; // буфер private int nextIndex; // свободная ячейка буфера public LeakyStack(int size) < data = (T[]) new Object[size]; >public void push(T value) < data[nextIndex++] = value; >public T pop() < return data[—nextIndex]; >>
При создании указывается максимальный размер стека и заводится массив, в котором будут храниться вставляемые элементы. Так как длину массива менять нельзя, сразу делаем массив размером с полный стек, чтобы у были ячейки про запас, а поле nextIndex будет указывать на первую свободную ячейку для хранения нового элемента. Схожие идеи используются во многих структурах данных, например, в ArrayList и ArrayDeque .
Пользоваться стеком можно так:
LeakyStack stack = new LeakyStack<>(10); stack.push(«Petya»); stack.push(«Katya»); System.out.println(stack.pop()); // Katya
Наша реализация приводит к утечке памяти. И речь не о наличии ячеек в массиве про запас, проблема тут гораздо серьёзнее. Если, как в нашем примере выше, пользователь положил объект «Katya» на стек, а затем вынул и никуда не сохранил, то он ожидает удаление этого объекта из памяти через GC. Но этого не произойдёт, тк в нашем массиве ссылка на этот объект продолжит храниться, предотвращая удаление мусора из кучи.
Избавиться от этого поможет об-null-ение ячеек, значения которых больше не нужны:
public T pop()
Итог
Наличие встроенных алгоритмов сборки мусора ещё не гарантирует что весь мусор будет вычищаться, а занятая им память освобождаться для переиспользования. Понимание принципов работы GC помогает избежать накопления такого мусора в программе, который не будет убран автоматически.
Источники
Источник: proglib.io