Управление памятью в Java
Обновлено: 21.11.2024
Присоединяйтесь к сообществу DZone и получите все возможности участника.
Вы можете подумать, что если вы программируете на Java, что вам нужно знать о том, как работает память? Java имеет автоматическое управление памятью, приятный и тихий сборщик мусора, который работает в фоновом режиме, очищая неиспользуемые объекты и освобождая память.
Поэтому вам, как Java-программисту, не нужно беспокоиться о таких проблемах, как уничтожение объектов, поскольку они больше не используются. Однако, даже если этот процесс в Java автоматический, он ничего не гарантирует. Не зная, как устроен сборщик мусора и память Java, вы можете иметь объекты, которые не подходят для сборки мусора, даже если вы их больше не используете.
Поэтому важно знать, как на самом деле работает память в Java, поскольку это дает вам преимущество в написании высокопроизводительных и оптимизированных приложений, которые никогда не будут аварийно завершать работу с ошибкой OutOfMemoryError . С другой стороны, когда вы окажетесь в плохой ситуации, вы сможете быстро найти утечку памяти.
Для начала давайте посмотрим, как обычно организована память в Java:
Как правило, память делится на две большие части: стек и куча. Пожалуйста, имейте в виду, что размер типов памяти на этом рисунке не пропорционален реальному размеру памяти. Куча — это огромный объем памяти по сравнению со стеком.
Стек
Память стека отвечает за хранение ссылок на объекты кучи и за хранение типов значений (также известных в Java как примитивные типы), которые содержат само значение, а не ссылку на объект из кучи.
Кроме того, переменные в стеке имеют определенную видимость, также называемую областью действия. Используются только объекты из активной области. Например, предполагая, что у нас нет глобальных переменных области видимости (полей), а есть только локальные переменные, если компилятор выполняет тело метода, он может получить доступ только к объектам из стека, которые находятся внутри тела метода. Он не может получить доступ к другим локальным переменным, поскольку они находятся вне области видимости. Как только метод завершится и вернется, верхняя часть стека выскакивает, а активная область изменяется.
Возможно, вы заметили, что на изображении выше показано несколько ячеек памяти стека. Это связано с тем, что память стека в Java выделяется для каждого потока. Поэтому каждый раз, когда поток создается и запускается, он имеет свою собственную память стека и не может получить доступ к памяти стека другого потока.
Куча
Эта часть памяти хранит реальный объект в памяти. На них ссылаются переменные из стека. Например, давайте проанализируем, что происходит в следующей строке кода:
Ключевое слово new отвечает за обеспечение достаточного количества свободного места в куче, создание в памяти объекта типа StringBuilder и обращение к нему через ссылку «builder», которая идет в стек.
Для каждого работающего процесса JVM существует только одна динамическая память. Следовательно, это общая часть памяти, независимо от того, сколько потоков запущено. На самом деле структура кучи немного отличается от той, что показана на рисунке выше. Сама куча разделена на несколько частей, что облегчает процесс сборки мусора.
Максимальный размер стека и кучи заранее не определены — это зависит от работающей машины. Однако позже в этой статье мы рассмотрим некоторые конфигурации JVM, которые позволят нам явно указать их размер для работающего приложения.
Ссылочные типы
Если вы внимательно посмотрите на рисунок Структура памяти, вы, вероятно, заметите, что стрелки, представляющие ссылки на объекты из кучи, на самом деле относятся к разным типам. Это потому, что в языке программирования Java у нас есть разные типы ссылок: сильные, слабые, мягкие и фантомные ссылки. Разница между типами ссылок заключается в том, что объекты в куче, на которые они ссылаются, подлежат сборке мусора по разным критериям. Давайте подробнее рассмотрим каждый из них.
1. Сильная ссылка
Это самые популярные типы ссылок, к которым мы все привыкли. В приведенном выше примере со StringBuilder мы фактически храним сильную ссылку на объект из кучи. Объект в куче не подвергается сборке мусора, пока на него указывает строгая ссылка или если он строго доступен через цепочку строгих ссылок.
2. Слабая ссылка
Проще говоря, слабая ссылка на объект из кучи, скорее всего, не сохранится после следующего процесса сборки мусора. Слабая ссылка создается следующим образом:
Хорошим примером использования слабых ссылок являются сценарии кэширования. Представьте, что вы извлекаете какие-то данные и хотите, чтобы они также хранились в памяти — те же самые данные могут быть запрошены снова. С другой стороны, вы не уверены, когда и будут ли эти данные запрошены снова.Таким образом, вы можете сохранить слабую ссылку на него, и в случае запуска сборщика мусора может случиться так, что он уничтожит ваш объект в куче. Поэтому через некоторое время, если вы захотите получить объект, на который ссылаетесь, вы можете внезапно получить нулевое значение. Хорошая реализация сценариев кэширования — коллекция WeakHashMap. Если мы откроем класс WeakHashMap в API Java, мы увидим, что его записи фактически расширяют класс WeakReference и используют его поле ref в качестве ключа карты:
После сборки ключа из WeakHashMap вся запись удаляется с карты.
3. Мягкая ссылка
Эти типы ссылок используются для более чувствительных к памяти сценариев, поскольку они будут удалены сборщиком мусора только тогда, когда вашему приложению не хватает памяти. Поэтому до тех пор, пока нет критической необходимости освобождать место, сборщик мусора не будет трогать мягко доступные объекты. Java гарантирует, что все программные объекты, на которые ссылаются, будут очищены до того, как будет выброшена ошибка OutOfMemoryError. В Javadocs указано: "все программные ссылки на программно доступные объекты гарантированно очищаются до того, как виртуальная машина выдаст ошибку OutOfMemoryError".
Подобно слабым ссылкам, мягкие ссылки создаются следующим образом:
4. Фантомная ссылка
Используется для планирования действий по очистке после смерти, поскольку мы точно знаем, что объекты больше не живы. Используется только с очередью ссылок, так как метод .get() таких ссылок всегда будет возвращать null . Эти типы ссылок считаются предпочтительными для финализаторов.
Как ссылаются на строки
Тип String в Java обрабатывается немного по-другому. Строки неизменяемы, а это означает, что каждый раз, когда вы что-то делаете со строкой, в куче фактически создается другой объект. Для строк Java управляет пулом строк в памяти. Это означает, что Java сохраняет и повторно использует строки, когда это возможно. В основном это верно для строковых литералов. Например:
При запуске выводит следующее:
Строки равны
Поэтому получается, что после сравнения двух ссылок типа String они на самом деле указывают на одни и те же объекты в куче. Однако это недействительно для вычисляемых строк. Предположим, что у нас есть следующее изменение в строке //1 приведенного выше кода
Строки разные
В этом случае мы фактически видим, что у нас есть два разных объекта в куче. Если учесть, что вычисляемая строка будет использоваться довольно часто, мы можем заставить JVM добавить ее в пул строк, добавив метод .intern() в конец вычисляемой строки:
Добавление указанного выше изменения приводит к следующему результату:
Строки равны
Процесс сбора мусора
Как обсуждалось ранее, в зависимости от типа ссылки, которую переменная из стека содержит на объект из кучи, в определенный момент времени этот объект становится доступным для сборщика мусора.
Например, все объекты, выделенные красным, могут быть собраны сборщиком мусора. Вы можете заметить, что в куче есть объект, который имеет сильные ссылки на другие объекты, которые также находятся в куче (например, это может быть список со ссылками на его элементы или объект с двумя полями типа, на которые ссылаются). Однако, поскольку ссылка из стека потеряна, доступ к ней больше невозможен, так что это тоже мусор.
Чтобы углубиться в детали, давайте сначала упомянем несколько вещей:
Этот процесс запускается Java автоматически, и от Java зависит, когда и нужно ли запускать этот процесс.
На самом деле это дорогостоящий процесс. Когда запускается сборщик мусора, все потоки в вашем приложении приостанавливаются (в зависимости от типа сборщика мусора, который будет обсуждаться позже).
На самом деле это более сложный процесс, чем просто сборка мусора и освобождение памяти.
Несмотря на то, что Java решает, когда запускать сборщик мусора, вы можете явно вызвать System.gc() и ожидать, что сборщик мусора запустится при выполнении этой строки кода, верно?
Это неверное предположение.
Вы только как бы просите Java запустить сборщик мусора, но, опять же, решать, делать это или нет. В любом случае, явный вызов System.gc() не рекомендуется.
Поскольку это довольно сложный процесс, который может повлиять на вашу производительность, он реализован разумно. Для этого используется так называемый процесс «Mark and Sweep». Java анализирует переменные из стека и «помечает» все объекты, которые необходимо поддерживать. Затем все неиспользуемые объекты удаляются.
Генерации динамической памяти
При создании объекта он размещается в пространстве Eden(1). Поскольку пространство Эдема не такое уж большое, оно довольно быстро заполняется. Сборщик мусора работает в пространстве Eden и помечает объекты как живые.
После того как объект переживает процесс сборки мусора, он перемещается в так называемое оставшееся пространство S0(2). Во второй раз, когда сборщик мусора запускается в пространстве Eden, он перемещает все уцелевшие объекты в пространство S1(3). Кроме того, все, что в данный момент находится на S0(2), перемещается в пространство S1(3).
Если объект выживает в течение X циклов сборки мусора (X зависит от реализации JVM, в моем случае это 8), наиболее вероятно, что он будет существовать вечно и будет перемещен в пространство Old(4).
С учетом всего сказанного выше, если вы посмотрите на график сборщика мусора (6), каждый раз, когда он запускается, вы увидите, что объекты переключаются на оставшееся пространство, а пространство Эдема увеличивается. И так далее. Сборщиком мусора может быть и старое поколение, но так как это большая часть памяти по сравнению с пространством Эдема, это случается не так часто. Metaspace(5) используется для хранения метаданных о ваших загруженных классах в JVM.
Представленное изображение на самом деле является приложением Java 8. До Java 8 структура памяти была немного другой. Метапространство на самом деле называется PermGen. пространство. Например, в Java 6 в этом пространстве также хранилась память для пула строк. Поэтому, если в вашем приложении Java 6 слишком много строк, это может привести к сбою.
Типы сборщиков мусора
На самом деле в JVM есть три типа сборщиков мусора, и программист может выбрать, какой из них следует использовать. По умолчанию Java выбирает тип используемого сборщика мусора в зависимости от используемого оборудования.
1. Последовательный сборщик мусора — коллектор с одним потоком. В основном применяется к небольшим приложениям с небольшим использованием данных. Можно включить, указав параметр командной строки: -XX:+UseSerialGC
2. Параллельный сборщик мусора. Даже из названия разница между Serial и Parallel заключается в том, что Parallel GC использует несколько потоков для выполнения процесса сборки мусора. Этот тип GC также известен как сборщик пропускной способности. Его можно включить, явно указав параметр: -XX:+UseParallelGC
3. В основном параллельный сборщик мусора. Если вы помните, ранее в этой статье упоминалось, что процесс сборки мусора на самом деле довольно дорог, и когда он выполняется, все потоки приостанавливаются. Однако у нас есть этот в основном параллельный тип GC, в котором указано, что он работает параллельно с приложением. Однако есть причина, по которой это «в основном» одновременно. Он не работает на 100% одновременно с приложением. Существует период времени, на который потоки приостанавливаются. Тем не менее, пауза должна быть как можно короче для достижения наилучшей производительности GC. На самом деле существует 2 типа в основном одновременных сборщиков мусора:
3.1 Garbage First — высокая пропускная способность с разумным временем паузы приложения. Включено с опцией: -XX:+UseG1GC
3.2 Параллельная проверка меток. Время паузы приложения сведено к минимуму. Его можно использовать, указав опцию: -XX:+UseConcMarkSweepGC. Начиная с JDK 9, этот тип GC устарел.
Советы и рекомендации
Чтобы свести к минимуму объем памяти, максимально ограничьте область действия переменных. Помните, что каждый раз, когда всплывает верхняя область из стека, ссылки из этой области теряются, и это может привести к тому, что объекты подлежат сборке мусора.
Явно ссылайтесь на нулевые устаревшие ссылки. Это сделает объекты, на которые ссылаются, доступными для сборки мусора .
Избегайте финализаторов. Они замедляют процесс и ничего не гарантируют. Предпочитайте фантомные ссылки для работы по очистке.
Не используйте сильные ссылки там, где применимы слабые или мягкие ссылки. Самые распространенные ловушки памяти — это сценарии кэширования, когда данные хранятся в памяти, даже если они могут быть не нужны.
JVisualVM также имеет функцию создания дампа кучи в определенный момент, чтобы вы могли анализировать для каждого класса, сколько памяти он занимает.
Настройте JVM в соответствии с требованиями вашего приложения. Явно укажите размер кучи для JVM при запуске приложения. Процесс выделения памяти также требует больших затрат, поэтому выделяйте разумный начальный и максимальный объем памяти для кучи. Если вы знаете, что не имеет смысла начинать с небольшого начального размера кучи с самого начала, JVM расширит это пространство памяти. Указание параметров памяти с помощью следующих параметров:
Начальный размер кучи -Xms512m — установите начальный размер кучи на 512 мегабайт.
Максимальный размер кучи -Xmx1024m — установите максимальный размер кучи на 1024 мегабайта.
Размер стека потоков -Xss1m – установите размер стека потоков равным 1 мегабайту.
Размер молодого поколения -Xmn256m — установите размер молодого поколения на 256 мегабайт.
Если приложение Java аварийно завершает работу с ошибкой OutOfMemoryError и вам нужна дополнительная информация для обнаружения утечки, запустите процесс с параметром –XX:HeapDumpOnOutOfMemory, который создаст файл дампа кучи, когда эта ошибка произойдет в следующий раз.
Используйте параметр -verbose:gc, чтобы получить выходные данные сборки мусора. Каждый раз, когда происходит сборка мусора, будут генерироваться выходные данные.
Заключение
Знание того, как организована память, позволяет писать хороший и оптимизированный код с точки зрения ресурсов памяти. Преимущество заключается в том, что вы можете настроить работающую JVM, предоставив различные конфигурации, наиболее подходящие для вашего работающего приложения. Выявить и устранить утечку памяти очень просто, если использовать правильные инструменты.
Управление памятью — это процесс выделения новых объектов и удаления неиспользуемых объектов, чтобы освободить место для этих новых объектов. В этом разделе представлены некоторые основные концепции управления памятью и объясняются основы выделения объектов и сборки мусора в JVM Oracle JRockit. Рассматриваются следующие темы:
Информацию о том, как использовать параметры командной строки для настройки системы управления памятью, см. в разделе Настройка системы управления памятью.
Куча и питомник
Объекты Java находятся в области, называемой куча. Куча создается при запуске JVM и может увеличиваться или уменьшаться в размере во время работы приложения. Когда куча заполняется, мусор собирается. Во время сборки мусора объекты, которые больше не используются, очищаются, освобождая место для новых объектов.
Обратите внимание, что JVM использует больше памяти, чем просто куча. Например, методы Java, стеки потоков и собственные дескрипторы размещаются в памяти отдельно от кучи, а также внутренних структур данных JVM.
Куча иногда делится на две области (или поколения), которые называются питомник (или молодое пространство) и старое пробел. Детская — это часть кучи, зарезервированная для размещения новых объектов. Когда питомник заполняется, сбор мусора осуществляется путем запуска специальной молодой коллекции, где все объекты, достаточно долго прожившие в питомнике, перемещаются (перемещаются) в старое пространство , тем самым освобождая детскую для размещения большего количества объектов. Когда старое пространство заполняется мусором, в нем собирается процесс, называемый старой коллекцией.
Смысл питомника в том, что большинство объектов временны и недолговечны. Молодая коллекция разработана таким образом, чтобы быстро находить вновь выделенные объекты, которые еще живы, и перемещать их из детской. Как правило, молодая коллекция освобождает заданный объем памяти намного быстрее, чем старая коллекция или сборка мусора из кучи с одним поколением (куча без питомника).
В R27.2.0 и более поздних версиях часть питомника зарезервирована как область хранения. Область хранения содержит самые последние выделенные объекты в питомнике и не очищается от мусора до следующей молодой коллекции. Это предотвращает повышение уровня объектов только потому, что они были выделены прямо перед запуском молодой коллекции.
Распределение объектов
Во время выделения объектов JVM JRockit различает маленькие и большие объекты. Ограничение, когда объект считается большим, зависит от версии JVM, размера кучи, стратегии сборки мусора и используемой платформы, но обычно составляет от 2 до 128 КБ. Дополнительные сведения см. в документации по параметрам -XXtlaSize и -XXlargeObjectLimit.
Небольшие объекты размещаются в локальных областях потока (TLA). Локальные области потока — это свободные фрагменты, зарезервированные из кучи и предоставленные потоку Java для монопольного использования. Затем поток может размещать объекты в своем TLA без синхронизации с другими потоками. Когда TLA заполняется, поток просто запрашивает новый TLA. TLA резервируются из питомника, если он существует, в противном случае они резервируются где угодно в куче.
Большие объекты, которые не помещаются в TLA, размещаются непосредственно в куче. При использовании детской крупные объекты размещаются непосредственно в старом пространстве. Выделение больших объектов требует большей синхронизации между потоками Java, хотя JRockit JVM использует систему кешей свободных фрагментов разного размера, чтобы уменьшить потребность в синхронизации и повысить скорость выделения.
Сборка мусора
Сборка мусора — это процесс освобождения места в куче или питомнике для размещения новых объектов. В этом разделе описывается сборка мусора в JRockit JVM.
Модель маркировки и развертки
Виртуальная машина JRockit JVM использует модель сборки мусора пометить и очистить для выполнения сборки мусора всей кучи. Сборка мусора с маркировкой и очисткой состоит из двух этапов: этапа маркировки и этапа этапа очистки.
На этапе пометки все объекты, доступные из потоков Java, собственные дескрипторы и другие корневые источники, помечаются как активные, а также объекты, доступные из этих объектов и т. д. Этот процесс идентифицирует и помечает все объекты, которые все еще используются, а остальные можно считать мусором.
Во время фазы сканирования куча просматривается, чтобы найти промежутки между живыми объектами. Эти промежутки записываются в свободный список и становятся доступными для нового размещения объектов.
JRockit JVM использует две улучшенные версии модели маркировки и развертки. Одна из них в основном параллельная маркировка и развертка, а другая — параллельная маркировка и развертка. Вы также можете комбинировать две стратегии, используя, например, параллельную маркировку и параллельную развертку.
В основном одновременная маркировка и очистка
Стратегия в основном параллельной пометки и очистки (часто называемая просто одновременной сборкой мусора) позволяет потокам Java продолжать работу во время больших частей сборки мусора. Однако потоки должны быть остановлены несколько раз для синхронизации.
Этап маркировки, в основном параллельный, разделен на четыре части:
-
Исходная маркировка, при которой идентифицируется корневой набор живых объектов. Это делается, когда потоки Java приостановлены. Параллельная маркировка, когда ссылки из корневого набора используются для поиска и маркировки остальных живых объектов в куче. Это делается во время выполнения потоков Java. Предварительная очистка, при которой идентифицируются изменения в куче на этапе параллельной маркировки, а также обнаруживаются и помечаются любые дополнительные активные объекты. Это делается во время выполнения потоков Java. Окончательная маркировка, при которой выявляются изменения на этапе предварительной очистки, а также обнаруживаются и отмечаются любые дополнительные живые объекты. Это делается, когда потоки Java приостановлены.
Фаза одновременных проверок состоит из четырех частей:
-
Подметание одной половины кучи. Это делается во время работы потоков Java, и им разрешено размещать объекты в той части кучи, которая в данный момент не очищается. Небольшая пауза для смены половинок. Подметание другой половины кучи. Это делается во время работы потоков Java, и им разрешено размещать объекты в той части кучи, которая была очищена первой. Небольшая пауза для синхронизации и записи статистики.
Параллельная маркировка и развертка
Стратегия параллельной маркировки и очистки (также называемая параллельным сборщиком мусора) использует все доступные ЦП в системе для максимально быстрой сборки мусора. Все потоки Java приостанавливаются во время всей параллельной сборки мусора.
Сборка мусора поколений
Питомник, если он существует, представляет собой сбор мусора специальной сборкой мусора, называемой молодой коллекцией. Стратегия сборки мусора, в которой используется питомник, называется генеральной стратегией сборки мусора или просто последовательной сборкой мусора.
Молодой сборщик, используемый в JVM JRockit, идентифицирует и продвигает все живые объекты в питомнике, которые находятся за пределами области хранения, в старое пространство. Эта работа выполняется параллельно с использованием всех доступных процессоров. Потоки Java приостанавливаются во время всей молодой коллекции.
Режимы динамической и статической сборки мусора
По умолчанию JRockit JVM использует режим динамической сборки мусора, который автоматически выбирает используемую стратегию сборки мусора с целью оптимизации пропускной способности приложения. Вы также можете выбрать один из двух других режимов динамической сборки мусора или выбрать стратегию сборки мусора статически. Доступны следующие динамические режимы:
-
пропускная способность, которая оптимизирует сборщик мусора для максимальной пропускной способности приложения. Это режим "по умолчанию". pausetime , который оптимизирует сборщик мусора для коротких и равномерных пауз. deterministic , оптимизирующий сборщик мусора для очень коротких и детерминированных пауз. Этот режим доступен только в составе Oracle JRockit Real Time.
-
singlepar , который представляет собой параллельный сборщик мусора с одним поколением (то же, что и parallel ) genpar , который представляет собой параллельный сборщик мусора с двумя поколениями singlecon , который представляет собой в основном параллельный сборщик мусора с одним поколением gencon , который представляет собой в основном параллельный сборщик мусора с двумя поколениями коллекционер
Дополнительную информацию о том, как выбрать лучший режим или стратегию для вашего приложения, см. в разделе Выбор и настройка сборщика мусора.
Сжатие
Объекты, размещенные рядом друг с другом, не обязательно станут недоступными ("умрут") одновременно. Это означает, что куча может быть фрагментирована после сборки мусора, так что свободного места в куче много, но мало, что делает выделение больших объектов трудным или даже невозможным.Свободное пространство, размер которого меньше минимального размера локальной области потока (TLA), вообще нельзя использовать, и сборщик мусора отбрасывает его как темную материю до тех пор, пока будущая сборка мусора не освободит достаточно места рядом с ним для создайте место, достаточно большое для TLA.
Чтобы уменьшить фрагментацию, JVM JRockit сжимает часть кучи при каждой сборке мусора (старая сборка). Сжатие перемещает объекты ближе друг к другу и дальше вниз в куче, тем самым создавая большие свободные области в верхней части кучи. Размер и положение области уплотнения, а также метод уплотнения выбираются с помощью расширенной эвристики в зависимости от используемого режима сборки мусора.
Сжатие выполняется в начале или во время фазы очистки и во время приостановки всех потоков Java.
Информацию о том, как настроить сжатие, см. в разделе Настройка сжатия памяти.
Внешнее и внутреннее уплотнение
JRockit JVM использует два метода сжатия: внешнее уплотнение и внутреннее уплотнение. Внешнее уплотнение перемещает (эвакуирует) объекты в пределах области уплотнения в свободные позиции за пределами области уплотнения и как можно ниже в куче. Внутреннее уплотнение перемещает объекты в пределах области уплотнения как можно ниже в области уплотнения, тем самым сближая их друг с другом.
JVM выбирает метод уплотнения в зависимости от текущего режима сборки мусора и положения области уплотнения. Внешнее уплотнение обычно используется в верхней части кучи, а внутреннее — в нижней части, где плотность объектов выше.
Схемы скользящего окна
Положение области уплотнения изменяется при каждой сборке мусора, используя одно или два скользящих окна для определения следующей позиции. Каждое скользящее окно перемещается на ступеньку вверх или вниз в куче при каждой сборке мусора, пока не достигнет другого конца кучи или не встретится со скользящим окном, которое движется в противоположном направлении, и начинается сначала. Таким образом, вся куча в конечном итоге проходится сжатием снова и снова.
Размер области уплотнения
Размер области уплотнения зависит от используемого режима сборки мусора. В пропускном режиме размер зоны уплотнения является статическим, в то время как во всех других режимах, включая статический, размер зоны уплотнения регулируется в зависимости от положения зоны уплотнения с целью поддержания одинакового времени уплотнения на протяжении всего цикла. Время уплотнения зависит от количества перемещенных объектов и количества ссылок на эти объекты. Таким образом, область уплотнения будет меньше в тех частях кучи, где плотность объектов высока или где велико количество ссылок на объекты в области. Обычно плотность объектов выше в нижней части кучи, чем в верхней части кучи, за исключением самого верха, где находятся самые последние выделенные объекты. Таким образом, области уплотнения обычно меньше в нижней части кучи, чем в верхней половине кучи.
Управление памятью в Java относится к выделению и освобождению памяти для объектов Java, которые находятся в областях, называемых стеком и кучей. В Java есть система автоматического освобождения памяти, известная как сборщик мусора. В этой статье представлены некоторые концепции управления памятью Java, работа сборщика мусора в JVM.
Что такое управление памятью в Java?
Управление памятью в Java — это процесс выделения пространства рабочей памяти для новых объектов и надлежащего удаления объектов, на которые нет ссылок, чтобы освободить место для этих новых объектов. Как правило, разработчикам не приходится иметь дело с обработкой памяти в Java напрямую, потому что сборщик мусора отвечает за освобождение памяти и запускается автоматически.
Зачем изучать управление памятью в Java?
Как мы уже знаем, Java сама управляет памятью и не требует специального вмешательства программистов. Сборщик мусора выполняет роль очистки объектов, которые больше не используются приложением, из рабочей памяти (ОЗУ).
Тогда зачем программистам изучать автоматическое управление памятью в Java? Хотя сборщик мусора считается автоматическим, он не гарантирует освобождение памяти/объектов, на которые все еще ссылаются. Программисты неосознанно оставляют ссылки на объекты даже после завершения их использования, что приводит к утечкам памяти или другим последствиям, с которыми не может справиться виртуальная машина Java (JVM). Программисты должны иметь представление об управлении памятью в Java, чтобы писать эффективные программы без утечек памяти.
Как программист, вы должны знать, какие объекты подлежат автоматической сборке мусора, а какие нет.Поэтому важно научиться управлять памятью, чтобы писать эффективные и высокопроизводительные программы с минимальной вероятностью сбоя.
Классификация или типы управления памятью Java
Для любого языка программирования рабочая память является жизненно важным ресурсом. Поэтому очень важно лучше понимать, как им управлять.
Основные концепции управления памятью Java:
1) структура памяти JVM
2) работа сборщика мусора.
Давайте обсудим их более подробно.
1) Структура памяти JVM
Модель памяти Java описывает, как потоки в языке программирования Java взаимодействуют через память. Структура памяти JVM имеет разные области, в которых находятся объекты программ.
Память JVM разделена на несколько частей:
Область методов
Область метода — это логическая часть области кучи, которая создается во время запуска виртуальной машины. Область методов предназначена для данных методов, структур классов, данных полей конструктора, а также для интерфейсов и специальных методов в классе.
В зависимости от конфигурации системы область метода может быть фиксированной или динамической, необходимой для вычислений. Это не должно быть заразно. Сборка мусора обязательна в куче, но область метода может быть или не быть сборщиком мусора, хотя она является логической частью области кучи.
Область кучи
Память кучи: область кучи — это общая область данных времени выполнения, которая может быть выделена для реальных объектов всех экземпляров классов и массивов. В зависимости от конфигурации системы размер кучи может быть динамическим или фиксированным. Он инициируется во время запуска виртуальной машины Java. Память кучи, выделенная объектам, освобождается сборщиком мусора.
По умолчанию максимальный размер кучи составляет 64 МБ. Когда вы используете ключевое слово new, объекту выделяется память в куче, и на его память ссылаются с помощью переменной из стека потока.
Для запущенного процесса виртуальной машины Java всегда существует только одна куча. Когда куча заполнена, сборщик мусора очищает объекты.
Параметры виртуальной машины для настройки размера кучи:
Чтобы установить максимальный размер кучи Java: -Xmx
Чтобы установить начальный размер кучи Java: -Xms
Память без кучи. В памяти JVM без кучи хранятся структуры для каждого класса, такие как пул констант времени выполнения, код для методов, данные полей и методов, интернированные строки и коды для конструкторов. Он также создается во время виртуальной машины. Как и в случае с кучей, максимальный объем памяти без кучи по умолчанию также составляет 64 МБ.
Опция виртуальной машины для настройки максимального размера — XX:MaxPermSize
Другая память: используется для хранения кода JVM, внутренней структуры JVM, данных и загруженного кода агента профилировщика и т. д.
Область стека JVM
Стек создается одновременно с созданием потока. Стек используется для хранения специфических для метода значений, которые недолговечны и ссылаются на другие объекты в куче. Память стека не обязательно должна быть заразной. Его размер может быть фиксированным или динамическим и может выбираться независимо при его создании. Переменные, хранящиеся в стеке, имеют определенную видимость, называемую областью действия.
Что такое кадр стека?
Фрейм стека — это структура данных потока. Данные потока представляют состояние потока в текущем методе.
- Фрейм стека хранит частичные результаты, а данные также выполняют динамическое связывание.
- Каждый кадр содержит собственный массив локальных переменных (LVA), данные кадра (FD) и стек операндов (OS).
- Размеры LVA, FD и OS определяются во время компиляции.
- В потоке управления одновременно активен только один кадр, т. е. текущий кадр. Этот кадр называется текущим кадром, а класс метода называется текущим классом.
- Если метод вызывает другой метод или метод завершен, фрейм останавливает текущий метод.
На фрейм может ссылаться только тот поток, который его создал.
Регистры счетчика программ (ПК)
Каждый созданный поток JVM имеет связанный с ним регистр ПК. Значение счетчика программ не определено в собственном методе, в то время как несобственный метод имеет счетчик программ, в котором хранится адрес инструкций JVM, выполняемых в данный момент. Он также хранит собственный указатель или адрес возврата.
Стек собственных методов
Стек собственных методов также известен как стек C. Нативные стеки методов написаны на других языках программирования, а не на java. Собственный интерфейс Java (JNI) вызывает собственный стек. Этот тип памяти выделяется потоку при его создании.Подобно другим типам, он может быть фиксированным или динамическим. Производительность собственного стека зависит от операционной системы.
2) Работа сборщика мусора
Когда программа запускается в Java, память для объектов выделяется в куче. Сборщик мусора пытается сохранить как можно больше свободной рабочей памяти. Куча — это единственная часть памяти Java, где возможна сборка мусора. Область кучи также известна как куча для сбора мусора, поскольку это единственный сегмент структуры памяти Java, где возможна сборка мусора.
Что такое сборка мусора Java?
Сборка мусора — это процесс сбора памяти, занимаемой объектами, на которые нет ссылок. Сборщик мусора управляется виртуальной машиной Java (JVM), JVM выбирает, когда запускать сборщик мусора. Сборка мусора выполняется всякий раз, когда JVM обнаруживает низкую доступность ресурсов рабочей памяти. JVM также учитывает наш запрос на сборку мусора. Но не всегда гарантируется, что JVM выполнит наш запрос.
Каковы критерии для объекта, подходящего для сборки мусора?
Каждый нессылочный объект в куче подходит для сборки мусора. Но что заставляет сборщик мусора думать, что на объект нет ссылок или нет ссылок? Поскольку Java запускает несколько потоков, все используемые объекты в куче доступны из кадров стека этих запущенных потоков. Любой объект, который недоступен ни одному потоку, считается лишенным ссылки и подлежит сборке мусора.
Процесс сбора мусора
- Виртуальная машина Java управляет процессом сборки мусора. Он запускает сборщик мусора, чтобы уменьшить нагрузку на программистов за счет освобождения памяти, занимаемой объектами, на которые нет ссылок.
- Сборка мусора приостанавливает другие процессы. Вы можете преодолеть эту проблему, применив несколько алгоритмов, основанных на сборщиках мусора, для повышения производительности программы. Этот процесс называется настройкой сборщика мусора.
- Другим решением может быть сборка мусора поколений. Объекты группируются в зависимости от того, сколько тактов они пережили (возраст). Таким образом распределяется работа Сборщика мусора.
Основные способы сбора мусора включают следующее.
<р>1. Маркировка: сборщик мусора идентифицирует объекты, присутствующие в рабочей памяти, которые больше не используются программой.2. Обычное удаление: сборщик мусора очищает объекты, которые не используются, и освобождает место в рабочей памяти для других объектов.
3. Удаление с уплотнением: чтобы повысить производительность выделения памяти для объектов новых программ, все уцелевшие объекты перемещаются вместе после сборки мусора.
Типы сборщиков мусора
JVM выбирает один из трех типов сборщиков мусора в зависимости от используемого оборудования и выполняет освобождение. Программисты также могут выбрать, какой сборщик мусора следует использовать.
Три типа сборщиков мусора:
1. Serial GC: это однопоточный сборщик, в основном используемый в небольших приложениях, использующих небольшие объемы данных.
Укажите данную командную строку для включения последовательного GC: -XX:+UseSerialGC
Вы можете включить параллельный сборщик мусора, просто указав данную командную строку: -XX:+UseParallelGC
3. В основном параллельный сборщик мусора: этот тип сборщика мусора работает одновременно с приложением. Тем не менее, это не 100% Concurrent, но время паузы потоков поддерживается как можно меньше для оптимизации производительности GC.
Два типа преимущественно параллельного GC:
<р>1. Garbage First: это сборщик с высокой пропускной способностью и разумным временем паузы.Включение командной строки: -XX:+UseG1GC <р>2. Concurrent Mark Sweep: CMS GC позволяет сократить время паузы в наихудшем случае, чем последовательный или параллельный GC, и там, где допустимо пожертвовать некоторой пропускной способностью приложения, чтобы устранить или значительно сократить количество длительных пауз GC.
Включить командную строку: -XX:+UseConcMarkSweepGC
Модель маркировки и развертки
Модель сборки мусора Mark and Sweep используется для очистки объектов всей кучи. Модель Mark and Sweep состоит из двух фаз: фазы маркировки и фазы развертки.
На этапе пометки все объекты, доступные из корневых источников, потоки Java помечаются как активные. Все объекты, которые все еще используются, идентифицируются, а остальные объекты считаются мусором.
На этапе очистки куча просматривается для поиска пробелов в памяти между используемыми объектами и записывается в список свободных мест. Затем эти промежутки становятся доступными для размещения новых объектов.
JRockit JVM использует две улучшенные версии модели Mark and Sweep. В основном это параллельная модель Mark and Sweep и параллельная Mark and Sweep.
Параллельная маркировка и развертка
Параллельная маркировка и очистка также известна как параллельная сборка мусора. Эта модель использует все доступные процессоры для максимально быстрой сборки мусора. В этой модели все потоки Java приостанавливаются на весь период сборки мусора.
В основном параллельная маркировка и очистка
Практически параллельная маркировка и очистка также известна как параллельная сборка мусора. Эта модель позволяет потоку Java продолжать работу даже во время длительных задач по сборке мусора. Однако поток должен несколько раз останавливаться для синхронизации.
Этап в основном параллельной маркировки:
<р>1. Начальная маркировка: корневой набор активных объектов идентифицируется, когда потоки Java приостановлены. <р>2. Параллельная маркировка: ссылки из корневого набора используются для поиска и маркировки других живых объектов. Это делается во время работы потоков Java. <р>3. Предварительная очистка: идентифицируются любые изменения, произошедшие на предыдущем шаге, а также обнаруживаются и отмечаются любые дополнительные живые объекты. Это делается во время работы потоков Java. <р>4. Окончательная маркировка: аналогично предварительной очистке, изменения во время предварительной очистки идентифицируются, а также обнаруживаются и помечаются любые дополнительные активные объекты, пока потоки Java приостановлены.Этап преимущественно параллельной проверки:
Очистка половины кучи. Потоки Java могут размещать объекты в той половине кучи, которая в данный момент не подвергается очистке. Это делается во время выполнения потоков Java
Очистка другой половины кучи: потокам Java разрешено размещать объекты в той половине кучи, которая была очищена первой. Это делается во время работы потоков Java.
- Снова делается короткая пауза для синхронизации и записи статистики.
Режимы динамической и статической сборки мусора
Режим динамической сборки мусора автоматически выбирает стратегию сборки мусора для оптимизации пропускной способности приложения. Программисты могут выбирать между динамическим и статическим режимами сборки мусора.
Ниже приведены доступные динамические режимы.
Детерминированный режим: оптимизирует сборщик мусора для очень коротких и детерминированных пауз.
Время паузы: оптимизация ГХ для коротких и равномерных пауз.
Пропускная способность. Динамический режим по умолчанию оптимизирует сборщик мусора для максимальной производительности приложения.
Ниже приведены основные статические стратегии.
Singlepar: это параллельный сборщик мусора с одним поколением, похожий на parallel.
Singlecon: Это однопоколенческий в основном параллельный сборщик мусора.
Genpar: Это параллельный сборщик мусора с двумя поколениями.
GenCon: это параллельный сборщик мусора с двумя поколениями.
Заключение
Управление кучей обеспечивает бесперебойную работу вашего приложения и устраняет вероятность ошибок нехватки памяти и сбоев системы. Четкое понимание того, как управляется память, поможет вам писать оптимизированные коды, эффективно использующие память.
Если вам интересно, чем мы занимаемся в Seagence, Seagence – это решение для мониторинга дефектов и автоматизации их устранения. Разработчики могут использовать Seagence, который выявляет все производственные дефекты с указанием их первопричин в режиме реального времени. У вас будет вся информация о дефекте вместе с контекстом, и вы исправите его без необходимости отладки. Чтобы узнать больше, свяжитесь с нами здесь!
Java считается зрелым и надежным языком программирования, и одной из причин этого являются его отличные возможности для управления памятью. В Java есть несколько вариантов управления памятью, обеспечивающих стабильную работу приложений. Это будет полное руководство по управлению памятью в Java. Мы рассмотрим некоторые важные темы, такие как работа кучи, ссылочные типы и сборка мусора.
Зачем вам знать об управлении памятью в Java?
Вам может быть интересно, зачем программисту знать о работе памяти? Java, как и любой другой современный язык высокого уровня, имеет автоматическое управление памятью и эффективный сборщик мусора, который очищает все неиспользуемые объекты и неявно управляет памятью.
Верно, разработчику Java не нужно выполнять какие-либо задачи, связанные с памятью, но, не зная, как работает сборщик мусора и как управляется память Java, вы можете в конечном итоге собрать объекты, которые не подходят для сборки мусора, даже если они вам больше не нужны.
Знание того, как управлять памятью в Java, позволяет вам писать высокопроизводительные и оптимизированные коды, которые не будут аварийно завершать работу с ошибкой OutOfMemoryError, а если вы когда-нибудь столкнетесь с ошибкой, связанной с памятью, вы сможете выявить проблемы.
Типы памяти в Java
В Java память делится на два больших блока: один – стек, а другой – куча.
1. Стек
Стековая память в основном отвечает за хранение всех ссылок на объекты, присутствующие в памяти кучи, и за хранение фактических значений примитивных типов данных.
Переменные в памяти стека имеют определенную видимость, известную как область действия. Методы используют только объекты с активной областью действия. Например, если у вас нет глобальных переменных области видимости, а есть только локальные переменные в вашем методе, когда он будет выполняться компилятором, он сможет получить доступ только к объектам из стека, которые присутствуют в теле метода. Он не может получить доступ к другим локальным переменным, поскольку они находятся вне области видимости. Как только метод будет выполнен и он вернет результат, активная область теперь будет меняться по мере того, как выдвигается вершина стека.
Следует отметить, что память стека в Java выделяется для каждого отдельного потока. Это означает, что всякий раз, когда поток создается и запускается, он получает новую долю памяти стека.
2. Куча
Куча — это место, где в памяти хранятся фактические объекты. Как следует из названия, это похоже на кучу и не имеет определенной структуры, такой как стек, поэтому на объекты затем ссылаются переменные, присутствующие в организованной памяти стека. Например, см. следующую строку кода:
Ключевое слово «new» проверяет, достаточно ли свободного места в куче, затем создает объект массива целых чисел в памяти и обращается к нему через ссылку «arrayResult», которая затем сохраняется в стеке. В отличие от памяти стека, для каждого процесса JVM существует только одна память кучи. Таким образом, это общая часть памяти, независимо от того, сколько потоков выполняется одновременно.
Типы ссылок
Что касается ссылок, в языке программирования Java существуют разные типы ссылок. Давайте рассмотрим каждый из них.
1. Сильная отсылка
Это наиболее часто используемый тип ссылки и ссылка по умолчанию на объекты в куче. Пример выше содержит сильную ссылку на объект из кучи. Сильная ссылка указывает на то, что объект не подлежит сборке мусора, пока на него указывает сильная ссылка.
2. Слабые ссылки
В отличие от сильных ссылок, объекты слабых ссылок не являются типом объектов ссылок по умолчанию, и при использовании их необходимо указывать явно. слабая ссылка на объект, скорее всего, используется для очистки объекта в следующем процессе сборки мусора. Слабая ссылка может быть создана следующим образом:
3. Мягкие ссылки
Мягкие ссылки используются для сценариев, чувствительных к памяти. Объекты с мягкими ссылками будут собираться мусором только тогда, когда вашему приложению не хватает памяти. Поэтому, пока нет критической необходимости освобождать место, сборщик мусора не будет касаться объектов с мягкой ссылкой.
Подобно слабым ссылкам, мягкие ссылки создаются следующим образом:
4. Фантомные ссылки
Объекты, на которые ссылаются фантомные ссылки, подлежат сборке мусора, но перед удалением JVM помещает их в очередь, называемую «очередью ссылок». Это делается после вызова для них метода finalize(), поэтому метод .get() таких ссылок всегда возвращает null, так как объект больше не находится в куче.
Что такое сбор мусора?
Сборка мусора — неотъемлемая часть управления памятью в Java. Сборщик мусора автоматически находит неиспользуемые объекты в куче и удаляет их, освобождая память.
Сборщики мусора выполняют следующую последовательность шагов.
- Сначала объекты, на которые нет ссылок, идентифицируются в куче и помечаются как готовые к сборке мусора.
- На следующем этапе отмеченные объекты удаляются из кучи.
- Последний шаг является необязательным, если доступная память сжимается после процесса сборки мусора, чтобы оставшиеся объекты находились в непрерывном блоке в начале кучи.
Процесс уплотнения упрощает выделение памяти новым объектам в последовательности после выделения блока памяти существующим объектам.
Ниже приведены некоторые важные моменты, связанные с управлением памятью в Java в отношении процесса сборки мусора,
- Процесс сборки мусора всегда запускается Java автоматически, и Java полностью решает, когда и следует ли запускать процесс сборки мусора. Разработчик может явно вызвать метод system.gc(), но это все равно, что попросить Java запустить процесс, если это возможно.
- Это может быть дорого с точки зрения использования ресурсов, так как при запуске сборщика мусора все потоки в вашем приложении приостанавливаются до его завершения.
- Это гораздо более сложный процесс, чем кажется, поскольку автоматическое удаление объекта включает в себя множество неявных шагов.
Типы сборщиков мусора
Это довольно сложный процесс, поэтому JVM имеет три типа сборщиков мусора. По умолчанию Java сама выбирает тип используемого сборщика мусора в зависимости от аппаратного обеспечения, но у разработчиков Java есть возможность выбрать, какой из них следует использовать.
Ниже приведены три типа сборщиков мусора,
1. Серийный сборщик мусора
Это однопоточный сборщик, в основном используемый для небольших приложений с незначительным использованием данных.
2. Параллельный сборщик мусора
Как следует из названия, параллельный сборщик мусора использует одновременно несколько потоков для выполнения процесса сборки мусора. Он также известен как сборщик пропускной способности и благодаря более высокой производительности является лучшим выбором для приложений с умеренным использованием данных.
3. В основном параллельный сборщик мусора
Это самый дорогой сборщик мусора из всех, так как при выполнении он приостанавливает все потоки. Причина, по которой он «в основном» параллелен, заключается в том, что он не работает одновременно с приложениями. Существует определенный период, в течение которого потоки приостанавливаются, и он должен быть как можно короче, чтобы достичь наилучшей производительности сборщика мусора и сделать его наименее затратным.
Существует еще 2 типа преимущественно параллельного GC:
1.1 Garbage First — высокая пропускная способность с разумным временем паузы приложения.
1.2 Одновременная очистка меток. В этом сборщике мусора приоритет отдается наименьшему времени паузы приложения, которое сведено к минимуму, насколько это возможно.
Несколько советов по лучшему управлению памятью в Java
- Чтобы свести объем памяти к минимуму, максимально ограничьте область действия переменных. Имейте в виду, что всякий раз, когда из стека извлекается верхняя область, ссылки из этой области теряются, что может привести к тому, что объекты подлежат сборке мусора.
- Явно ссылайтесь на нулевые устаревшие ссылки. Это сделает эти объекты доступными для сборки мусора.
- Избегайте финализаторов, так как они замедляют процесс, вместо этого используйте фантомные ссылки для очистки.
- Всегда используйте слабые или мягкие ссылки, когда это необходимо, вместо сильных ссылок по умолчанию.
- JVisualVM также предлагает возможность создания дампа кучи в определенные моменты времени. Используйте это, чтобы проанализировать, сколько памяти занимает каждый класс.
Заключение
Понимание того, как работает управление памятью в Java, поможет вам писать эффективные и оптимизированные программы. Теперь вы можете настроить свою JVM, используя наиболее подходящую конфигурацию для вашего работающего приложения, и легко выявлять проблемы с памятью и утечки.
Читайте также: