Управление памятью в java
Для оптимальной работы приложения JVM делит память на область стека (stack) и область кучи (heap). Всякий раз, когда мы объявляем новые переменные, создаем объекты или вызываем новый метод, JVM выделяет память для этих операций в стеке или в куче.
В этой статье мы рассмотрим эти две области памяти: обозначим ключевые отличия между ними, рассмотрим, как они используют память, изучим предлагаемые ими функции и как их использовать.
Стек работает по схеме LIFO (последним вошел, первым вышел). Всякий раз, когда вызывается новый метод, содержащий примитивные значения или ссылки на объекты, то на вершине стека под них выделяется блок памяти. Из этого можно сделать вывод, что стек хранит значения примитивных переменных, создаваемых в методах, а также ссылки на объекты в куче на которые ссылается метод.
Когда метод завершает выполнение, блок памяти (frame), отведенный для его нужд, очищается, и пространство становится доступным для следующего метода. При этом поток выполнения программы возвращается к месту вызова этого метода с последующим переходом к следующей строке кода.
Помимо того, что мы рассмотрели, существуют и другие особенности стека:
- Он заполняется и освобождается по мере вызова и завершения новых методов
- Переменные в стеке существуют до тех пор, пока выполняется метод в котором они были созданы
- Если память стека будет заполнена, Java бросит исключение java.lang.StackOverFlowError
- Доступ к этой области памяти осуществляется быстрее, чем к куче
- Является потокобезопасным, поскольку для каждого потока создается свой отдельный стек
Эта область памяти используется для динамического выделения памяти для объектов и классов JRE во время выполнения. Новые объекты всегда создаются в куче, а ссылки на них хранятся в стеке.
Эти объекты имеют глобальный доступ и могут быть получены из любого места программы.
Эта область памяти разбита на несколько более мелких частей, называемых поколениями:
Это глубокое погружение в управление памятью Java позволит расширить ваши знания о том, как работает куча, ссылочные типы и сборка мусора.
Вероятно, вы могли подумать, что если вы программируете на Java, то вам незачем знать о том, как работает память. В Java есть автоматическое управление памятью, красивый и тихий сборщик мусора, который работает в фоновом режиме для очистки неиспользуемых объектов и освобождения некоторой памяти.
Поэтому вам, как программисту на Java, не нужно беспокоиться о таких проблемах, как уничтожение объектов, поскольку они больше не используются. Однако, даже если в Java этот процесс выполняется автоматически, он ничего не гарантирует. Не зная, как устроен сборщик мусора и память Java, вы можете создать объекты, которые не подходят для сбора мусора, даже если вы их больше не используете.
Поэтому важно знать, как на самом деле работает память в Java, поскольку это дает вам преимущество в написании высокопроизводительных и оптимизированных приложений, которые никогда не будут аварийно завершены с ошибкой OutOfMemoryError . С другой стороны, когда вы окажетесь в плохой ситуации, вы сможете быстро найти утечку памяти.
Для начала давайте посмотрим, как обычно организована память в Java:
Структура памяти
Обычно память делится на две большие части: стек и куча. Имейте в виду, что размер типов памяти на этом рисунке не пропорционален реальному размеру памяти. Куча - это огромный объем памяти по сравнению со стеком.
Стек (Stack)
Стековая память отвечает за хранение ссылок на объекты кучи и за хранение типов значений (также известных в Java как примитивные типы), которые содержат само значение, а не ссылку на объект из кучи.
Кроме того, переменные в стеке имеют определенную видимость, также называемую областью видимости. Используются только объекты из активной области. Например, предполагая, что у нас нет никаких глобальных переменных (полей) области видимости, а только локальные переменные, если компилятор выполняет тело метода, он может получить доступ только к объектам из стека, которые находятся внутри тела метода. Он не может получить доступ к другим локальным переменным, так как они не выходят в область видимости. Когда метод завершается и возвращается, верхняя часть стека выталкивается, и активная область видимости изменяется.
Возможно, вы заметили, что на картинке выше отображено несколько стеков памяти. Это связано с тем, что стековая память в Java выделяется для каждого потока. Следовательно, каждый раз, когда поток создается и запускается, он имеет свою собственную стековую память и не может получить доступ к стековой памяти другого потока.
Куча (Heap)
Эта часть памяти хранит в памяти фактические объекты, на которые ссылаются переменные из стека. Например, давайте проанализируем, что происходит в следующей строке кода:
Ключевое слово new несет ответственность за обеспечение того, достаточно ли свободного места на куче, создавая объект типа StringBuilder в памяти и обращаясь к нему через «Builder» ссылки, которая попадает в стек.
Для каждого запущенного процесса JVM существует только одна область памяти в куче. Следовательно, это общая часть памяти независимо от того, сколько потоков выполняется. На самом деле структура кучи немного отличается от того, что показано на картинке выше. Сама куча разделена на несколько частей, что облегчает процесс сборки мусора.
Максимальные размеры стека и кучи не определены заранее - это зависит от работающей JVM машины. Позже в этой статье мы рассмотрим некоторые конфигурации JVM, которые позволят нам явно указать их размер для запускаемого приложения.
Типы ссылок
Если вы внимательно посмотрите на изображение структуры памяти, вы, вероятно, заметите, что стрелки, представляющие ссылки на объекты из кучи, на самом деле относятся к разным типам. Это потому, что в языке программирования Java используются разные типы ссылок: сильные, слабые, мягкие и фантомные ссылки. Разница между типами ссылок заключается в том, что объекты в куче, на которые они ссылаются, имеют право на сборку мусора по различным критериям. Рассмотрим подробнее каждую из них.
1. Сильная ссылка
Это самые популярные ссылочные типы, к которым мы все привыкли. В приведенном выше примере со StringBuilder мы фактически храним сильную ссылку на объект из кучи. Объект в куче не удаляется сборщиком мусора, пока на него указывает сильная ссылка или если он явно доступен через цепочку сильных ссылок.
2. Слабая ссылка
Попросту говоря, слабая ссылка на объект из кучи, скорее всего, не сохранится после следующего процесса сборки мусора. Слабая ссылка создается следующим образом:
Хорошим вариантом использования слабых ссылок являются сценарии кеширования. Представьте, что вы извлекаете некоторые данные и хотите, чтобы они также были сохранены в памяти - те же данные могут быть запрошены снова. С другой стороны, вы не уверены, когда и будут ли эти данные запрашиваться снова. Таким образом, вы можете сохранить слабую ссылку на него, и в случае запуска сборщика мусора, возможно, он уничтожит ваш объект в куче. Следовательно, через некоторое время, если вы захотите получить объект, на который вы ссылаетесь, вы можете внезапно получить null значение. Хорошей реализацией сценариев кеширования является коллекция WeakHashMap <K, V>. Если мы откроем WeakHashMap класс в Java API, мы увидим, что его записи фактически расширяют WeakReference класс и используют его поле ref в качестве ключа отображения ( Map) :
После сбора мусора ключа из WeakHashMap вся запись удаляется из карты.
3. Мягкая ссылка
Эти типы ссылок используются для более чувствительных к памяти сценариев, поскольку они будут собираться сборщиком мусора только тогда, когда вашему приложению не хватает памяти. Следовательно, пока нет критической необходимости в освобождении некоторого места, сборщик мусора не будет касаться легко доступных объектов. Java гарантирует, что все объекты, на которые имеются мягкие ссылки, будут очищены до того, как будет выдано исключение OutOfMemoryError . В документации Javadocs говорится, что «все мягкие ссылки на мягко достижимые объекты гарантированно очищены до того, как виртуальная машина выдаст OutOfMemoryError».
Подобно слабым ссылкам, мягкая ссылка создается следующим образом:
4. Фантомная ссылка
Используется для планирования посмертных действий по очистке, поскольку мы точно знаем, что объекты больше не живы. Используется только с очередью ссылок, поскольку .get() метод таких ссылок всегда будет возвращаться null . Эти типы ссылок считаются предпочтительными для финализаторов.
Ссылки на String
Ссылки на тип String в Java обрабатываются немного по- другому. Строки неизменяемы, что означает, что каждый раз, когда вы делаете что-то со строкой, в куче фактически создается другой объект. Для строк Java управляет пулом строк в памяти. Это означает, что Java сохраняет и повторно использует строки, когда это возможно. В основном это верно для строковых литералов. Например:
При запуске этот код распечатывает следующее:
Strings are equal
Следовательно, оказывается, что две ссылки типа String на одинаковые строковые литералы фактически указывают на одни и те же объекты в куче. Однако это не действует для вычисляемых строк. Предположим, что у нас есть следующее изменение в строке // 1 приведенного выше кода.
Strings are different
В этом случае мы фактически видим, что у нас есть два разных объекта в куче. Если учесть, что вычисляемая строка будет использоваться довольно часто, мы можем заставить JVM добавить ее в пул строк, добавив .intern() метод в конец вычисляемой строки:
При добавлении вышеуказанного изменения создается следующий результат:
Процесс сборки мусора
Как обсуждалось ранее, в зависимости от типа ссылки, которую переменная из стека содержит на объект из кучи, в определенный момент времени этот объект становится подходящим для сборщика мусора.
Объекты, подходящие для сборки мусора
Например, все объекты, отмеченные красным цветом, могут быть собраны сборщиком мусора. Вы можете заметить, что в куче есть объект, который имеет строгие ссылки на другие объекты, которые также находятся в куче (например, это может быть список, который имеет ссылки на его элементы, или объект, имеющий два поля типа, на которые есть ссылки). Однако, поскольку ссылка из стека потеряна, к ней больше нельзя получить доступ, так что это тоже мусор.
Чтобы углубиться в детали, давайте сначала упомянем несколько вещей:
Этот процесс запускается автоматически Java, и Java решает, запускать или нет этот процесс.
На самом деле это дорогостоящий процесс. При запуске сборщика мусора все потоки в вашем приложении приостанавливаются (в зависимости от типа GC, который будет обсуждаться позже).
На самом деле это более сложный процесс, чем просто сбор мусора и освобождение памяти.
Несмотря на то, что Java решает, когда запускать сборщик мусора, вы можете явно вызвать System.gc() и ожидать, что сборщик мусора будет запускаться при выполнении этой строки кода, верно?
Это ошибочное предположение.
Вы только как бы просите Java запустить сборщик мусора, но, опять же, Java решать, делать это или нет. В любом случае явно вызывать System.gc() не рекомендуется.
Поскольку это довольно сложный процесс и может повлиять на вашу производительность, он реализован разумно. Для этого используется так называемый процесс «Mark and Sweep». Java анализирует переменные из стека и «отмечает» все объекты, которые необходимо поддерживать в рабочем состоянии. Затем все неиспользуемые объекты очищаются.
Так что на самом деле Java не собирает мусор. Фактически, чем больше мусора и чем меньше объектов помечены как живые, тем быстрее идет процесс. Чтобы сделать это еще более оптимизированным, память кучи на самом деле состоит из нескольких частей. Мы можем визуализировать использование памяти и другие полезные вещи с помощью JVisualVM, инструмента, поставляемого с Java JDK. Единственное, что вам нужно сделать, это установить плагин с именем Visual GC, который позволяет увидеть, как на самом деле структурирована память. Давайте немного увеличим масштаб и разберем общую картину:
Поколения памяти кучи
Когда объект создается, он размещается в пространстве Eden (1). Поскольку пространство Eden не такое уж большое, оно заполняется довольно быстро. Сборщик мусора работает в пространстве Eden и помечает объекты как живые.
Если объект выживает в процессе сборки мусора, он перемещается в так называемое пространство выжившего S0(2). Во второй раз, когда сборщик мусора запускается в пространстве Eden, он перемещает все уцелевшие объекты в пространство S1(3). Кроме того, все, что в настоящее время находится на S0(2), перемещается в пространство S1(3).
Если объект выживает в течение X раундов сборки мусора (X зависит от реализации JVM, в моем случае это 8), скорее всего, он выживет вечно и перемещается в пространство Old(4).
Принимая все сказанное выше, если вы посмотрите на график сборщика мусора (6), каждый раз, когда он запускается, вы можете увидеть, что объекты переключаются на пространство выживших и что пространство Эдема увеличивалось. И так далее. Старое поколение также может быть обработано сборщиком мусора, но, поскольку это большая часть памяти по сравнению с пространством Eden, это происходит не так часто. Метапространство (5) используется для хранения метаданных о ваших загруженных классах в JVM.
Представленное изображение на самом деле является приложением Java 8. До Java 8 структура памяти была немного другой. Метапространство на самом деле называется PermGen область. Например, в Java 6 это пространство также хранит память для пула строк. Поэтому, если в вашем приложении Java 6 слишком много строк, оно может аварийно завершить работу.
Типы сборщиков мусора
Фактически, JVM имеет три типа сборщиков мусора, и программист может выбрать, какой из них следует использовать. По умолчанию Java выбирает используемый тип сборщика мусора в зависимости от базового оборудования.
1. Serial GC (Последовательный сборщик мусора) - однониточный коллектор. В основном относится к небольшим приложениям с небольшим использованием данных. Можно включить, указав параметр командной строки: -XX:+UseSerialGC.
2. Parallel GC (Параллельный сборщик мусора) - даже по названию, разница между последовательным и параллельным будет заключаться в том, что параллельный сборщик мусора использует несколько потоков для выполнения процесса сбора мусора. Этот тип GC также известен как сборщик производительности. Его можно включить, явно указав параметр: -XX:+UseParallelGC.
3. Mostly concurrent GC (В основном параллельный сборщик мусора). Если вы помните, ранее в этой статье упоминалось, что процесс сбора мусора на самом деле довольно дорогостоящий, и когда он выполняется, все потоки приостанавливаются. Однако у нас есть в основном параллельный тип GC, который утверждает, что он работает одновременно с приложением. Однако есть причина, по которой он «в основном» параллелен. Он не работает на 100% одновременно с приложением. Есть период времени, на который цепочки приостанавливаются. Тем не менее, пауза делается как можно короче для достижения наилучшей производительности сборщика мусора. На самом деле существует 2 типа в основном параллельных сборщиков мусора:
3.1 Garbage First - высокая производительность с разумным временем паузы приложения. Включено с опцией: -XX:+UseG1GC.
3.2 Concurrent Mark Sweep (Параллельное сканирование отметок) - время паузы приложения сведено к минимуму. Он может быть использован с помощью опции: -XX:+UseConcMarkSweepGC . Начиная с JDK 9, этот тип GC объявлен устаревшим.
Советы и приемы
Чтобы минимизировать объем памяти, максимально ограничьте область видимости переменных. Помните, что каждый раз, когда выскакивает верхняя область видимости из стека, ссылки из этой области теряются, и это может сделать объекты пригодными для сбора мусора.
Явно устанавливайте в null устаревшие ссылки. Это сделает объекты, на которые ссылаются, подходящими для сбора мусора.
Избегайте финализаторов (finalizer). Они замедляют процесс и ничего не гарантируют. Фантомные ссылки предпочтительны для работы по очистке памяти.
Не используйте сильные ссылки там, где можно применить слабые или мягкие ссылки. Наиболее распространенные ошибки памяти - это сценарии кэширования, когда данные хранятся в памяти, даже если они могут не понадобиться.
JVisualVM также имеет функцию создания дампа кучи в определенный момент, чтобы вы могли анализировать для каждого класса, сколько памяти он занимает.
Настройте JVM в соответствии с требованиями вашего приложения. Явно укажите размер кучи для JVM при запуске приложения. Процесс выделения памяти также является дорогостоящим, поэтому выделите разумный начальный и максимальный объем памяти для кучи. Если вы знаете его, то не имеет смысла начинать с небольшого начального размера кучи с самого начала, JVM расширит это пространство памяти. Указание параметров памяти выполняется с помощью следующих параметров:
Начальный размер кучи -Xms512m - установите начальный размер кучи на 512 мегабайт.
Максимальный размер кучи -Xmx1024m - установите максимальный размер кучи 1024 мегабайта.
Размер стека потоков -Xss1m - установите размер стека потоков равным 1 мегабайту.
Размер поколения -Xmn256m - установите размер поколения 256 мегабайт.
Если приложение Java выдает ошибку OutOfMemoryError и вам нужна дополнительная информация для обнаружения утечки, запустите процесс с –XX:HeapDumpOnOutOfMemory параметром, который создаст файл дампа кучи, когда эта ошибка произойдет в следующий раз.
Используйте опцию -verbose:gc , чтобы получить вывод процесса сборки мусора. Каждый раз, когда происходит сборка мусора, будет генерироваться вывод.
Заключение
Знание того, как организована память, дает вам преимущество в написании хорошего и оптимизированного кода с точки зрения ресурсов памяти. Преимущество заключается в том, что вы можете настроить свою работающую JVM, предоставив различные конфигурации, наиболее подходящие для запуска вашего приложения. Выявление и устранение утечек памяти - это очень просто, если использовать правильные инструменты.
Управление памятью на Java. Модель памяти Java. Модель памяти JVM. Распределение памяти Java, Молодое поколение, Старое поколение, Постоянное поколение, Куча, Стек.
Понимание Модели памяти JVM , Управления памятью Java очень важно, если вы хотите понять работу Сборки мусора Java . Сегодня мы рассмотрим управление памятью в Java, различные части памяти JVM и то, как отслеживать и выполнять настройку сборки мусора.
Модель памяти Java (JVM)
Как вы можете видеть на приведенном выше изображении, память JVM разделена на отдельные части. На широком уровне память кучи JVM физически разделена на две части – Молодое поколение и Старое поколение .
Управление памятью на Java – Молодое поколение
Молодое поколение-это место, где создаются все новые объекты. Когда молодое поколение заполняется, производится сбор мусора. Эта сборка мусора называется Minor GC . Молодое поколение разделено на три части – Память Эдема и две Память выживших пространства.
Важные моменты, касающиеся пространств для молодого поколения:
- Большинство вновь созданных объектов находятся в пространстве памяти Eden.
- Когда пространство Eden заполняется объектами, выполняется незначительная сборка, и все объекты, оставшиеся в живых, перемещаются в одно из пространств, оставшихся в живых.
- Второстепенный GC также проверяет объекты выживших и перемещает их в другое пространство выживших. Таким образом, за один раз одно из оставшихся в живых мест всегда пустует.
- Объекты, которые сохранились после многих циклов GC, перемещаются в пространство памяти старого поколения. Обычно это делается путем установления порогового значения возраста объектов молодого поколения, прежде чем они получат право на продвижение к старому поколению.
Управление памятью в Java – Старое поколение
Память старого поколения содержит объекты, которые являются долгоживущими и сохранились после многих раундов незначительной GC. Обычно сборка мусора выполняется в памяти старого поколения, когда она заполнена. Сборка мусора старого поколения называется Major GC и обычно занимает больше времени.
Остановите Мировое событие
Поскольку молодое поколение хранит недолговечные объекты, незначительная GC выполняется очень быстро, и это не влияет на приложение.
Однако основная GC занимает много времени, потому что она проверяет все живые объекты. Основные операции GC должны быть сведены к минимуму, поскольку это приведет к тому, что ваше приложение не будет отвечать на запросы во время сбора мусора. Поэтому, если у вас есть отзывчивое приложение и происходит много крупных операций по сбору мусора, вы заметите ошибки тайм-аута.
Продолжительность, затрачиваемая сборщиком мусора, зависит от стратегии, используемой для сбора мусора. Вот почему необходимо отслеживать и настраивать сборщик мусора, чтобы избежать тайм-аутов в приложениях с высокой чувствительностью.
Модель памяти Java – Постоянное поколение
Пермское поколение заполняется JVM во время выполнения на основе классов, используемых приложением. Perm Gen также содержит классы и методы библиотеки Java SE. Объекты пермского поколения-это мусор, собранный в полной сборке мусора.
Модель памяти Java – Область Методов
Область методов является частью пространства в PermGen и используется для хранения структуры классов (констант времени выполнения и статических переменных) и кода для методов и конструкторов.
Модель памяти Java – Пул памяти
Пулы памяти создаются менеджерами памяти JVM для создания пула неизменяемых объектов, если реализация поддерживает это. Пул строк-хороший пример такого пула памяти. Пул памяти может принадлежать куче или PermGen, в зависимости от реализации диспетчера памяти JVM.
Модель памяти Java – Пул констант времени Выполнения
Пул констант времени выполнения-это представление во время выполнения для каждого класса пула констант в классе. Он содержит константы времени выполнения класса и статические методы. Пул констант времени выполнения является частью области методов.
Модель памяти Java – Память стека Java
Память стека Java используется для выполнения потока. Они содержат кратковременные значения для конкретного метода и ссылки на другие объекты в куче, на которые ссылается метод. Вы должны прочитать Разницу между стековой и кучной памятью .
Управление памятью в Java – Переключатели памяти кучи Java
Java предоставляет множество переключателей памяти, которые мы можем использовать для установки размеров памяти и их соотношений. Некоторые из часто используемых переключателей памяти являются:
-Xms | Для установки начального размера кучи при запуске JVM |
-Xmx | Для установки максимального размера кучи. |
-Xmn | Для определения численности молодого поколения остальная часть пространства отводится Старому поколению. |
-XX:ПермГен | Для установки начального размера постоянной памяти поколения |
-XX:MaxPermGen | Для установки максимального размера PermGen |
-XX:Выживание | Для обеспечения соотношения пространства Эдема и пространства Выживших, например, если размер молодого поколения составляет 10 м, а переключатель виртуальной машины равен 5 м, то для пространства Эдема будет зарезервировано 5 м, а для обоих пространств Выживших-по 2,5 м. Значение по умолчанию равно 8. |
-XX:Новое время | Для обеспечения соотношения размеров старого/нового поколения. Значение по умолчанию равно 2. |
В большинстве случаев вышеуказанных опций достаточно, но если вы хотите проверить и другие опции, пожалуйста, проверьте Официальную страницу опций JVM .
Управление памятью в Java – Сборка мусора Java
Сборка мусора Java-это процесс идентификации и удаления неиспользуемых объектов из памяти и свободного пространства, которые будут выделены объектам, созданным в ходе будущей обработки. Одной из лучших особенностей языка программирования Java является автоматическая сборка мусора , в отличие от других языков программирования, таких как C, где выделение и освобождение памяти выполняется вручную.
Один из основных способов сбора мусора включает в себя три этапа:
- Маркировка : Это первый шаг, на котором сборщик мусора определяет, какие объекты используются, а какие нет.
- Обычное удаление : Сборщик мусора удаляет неиспользуемые объекты и освобождает свободное пространство, которое будет выделено другим объектам.
- Удаление с уплотнением : Для повышения производительности после удаления неиспользуемых объектов все уцелевшие объекты можно переместить, чтобы они были вместе. Это повысит производительность выделения памяти более новым объектам.
Есть две проблемы с простым подходом к пометке и удалению.
Вышеуказанные недостатки при простом подходе являются причиной того, что Сборка мусора Java является поколенческой и у нас есть Молодое поколение и Старое поколение места в памяти кучи. Я уже объяснял выше, как объекты сканируются и перемещаются из одного пространства поколений в другое на основе второстепенного GC и Основного GC.
Управление памятью в Java – Типы сборки мусора Java
Существует пять типов сбора мусора, которые мы можем использовать в наших приложениях. Нам просто нужно использовать переключатель JVM, чтобы включить стратегию сбора мусора для приложения. Давайте рассмотрим каждый из них по очереди.
Управление памятью в Java – Мониторинг сборки мусора Java
Мы можем использовать командную строку Java, а также инструменты пользовательского интерфейса для мониторинга действий по сбору мусора в приложении. Для моего примера я использую одно из демонстрационных приложений, предоставляемых загрузками Java SE.
Если вы хотите использовать одно и то же приложение, перейдите на страницу Загрузки Java SE и загрузите Демонстрации и образцы JDK 7 и JavaFX . Пример приложения, которое я использую, это Java2Demo.jar и он присутствует в каталоге jdk1.7.0_55/demo/jfc/Java2D . Однако это необязательный шаг, и вы можете запускать команды мониторинга GC для любого приложения java.
Команда, используемая мной для запуска демонстрационного приложения, является:
jstat
Мы можем использовать jstat инструмент командной строки для мониторинга памяти JVM и операций по сбору мусора. Он поставляется со стандартным JDK, так что вам не нужно ничего делать, чтобы его получить.
Для выполнения jstat вам необходимо знать идентификатор процесса приложения, вы можете легко его получить, используя команду ps-eaf | grep java .
Последним аргументом для jstat является интервал времени между каждым выводом, поэтому он будет печатать данные о памяти и сборке мусора каждые 1 секунду.
Давайте пройдемся по каждой колонке по очереди.
- S0C и S1C : В этом столбце показан текущий размер областей Выжившего 0 и Выжившего 1 в КБ.
- S0 и S1U : В этом столбце показано текущее использование областей Выживший 0 и Выживший 1 в КБ. Обратите внимание, что одна из областей выживших все время пуста.
- ЕС и ЕС : Эти столбцы показывают текущий размер и использование пространства Eden в КБ. Обратите внимание, что размер ЕС увеличивается, и как только он пересекает ЕС, вызывается Второстепенный GC и размер ЕС уменьшается.
- OC и OU : Эти столбцы показывают текущий размер и текущее использование старого поколения в КБ.
- PC и PU : В этих столбцах показан текущий размер и текущее использование PermGen в КБ.
- YGC и YGC : в столбце YGC отображается количество событий GC, произошедших в молодом поколении. В столбце YGCT отображается накопленное время для операций GC для молодого поколения. Обратите внимание, что оба они увеличены в одной строке, где значение EU уменьшается из-за незначительного GC.
- FGC и FGC : в столбце FGC отображается количество произошедших полных событий GC. В столбце FGCT отображается накопленное время для полных операций GC. Обратите внимание, что полное время GC слишком велико по сравнению с временем GC молодого поколения.
- GCT : В этом столбце отображается общее накопленное время для операций GC. Обратите внимание, что это сумма значений столбцов GCT и GCT.
Преимущество jstat в том, что он также может быть выполнен на удаленных серверах, где у нас нет графического интерфейса. Обратите внимание, что сумма S0C, S1C и EC составляет 10 м, как указано в параметре -Xmn10m JVM.
Java VisualVM с визуальным GC
Если вы хотите видеть операции с памятью и GC в графическом интерфейсе, вы можете использовать jvisualvm инструмент. Java VisualVM также является частью JDK, поэтому вам не нужно загружать его отдельно.
Просто запустите команду jvisualvm в терминале, чтобы запустить приложение Java VisualVM. После запуска вам необходимо установить Visual GC плагин с помощью опции Tools -< Плагины, как показано на рисунке ниже.
После установки Visual GC просто откройте приложение в левой боковой колонке и перейдите в раздел Visual GC . Вы получите изображение памяти JVM и сведения о сборке мусора, как показано на рисунке ниже.
Настройка Сборки мусора Java
Настройка сборки мусора Java должна быть последней опцией, которую вы должны использовать для увеличения пропускной способности вашего приложения, и только тогда, когда вы видите снижение производительности из-за более длительных периодов времени GC, вызывающих тайм-аут приложения.
Если вы видите java.lang.OutOfMemoryError: пространство PermGen ошибки в журналах, затем попробуйте отслеживать и увеличивать пространство памяти PermGen, используя параметры-XX:PermGen и -XX:MaxPermGen JVM. Вы также можете попробовать использовать -XX:+CMSClassUnloadingEnabled и проверить, как он работает с сборщиком мусора CMS.
Если вы видите много полных операций GC, то вам следует попробовать увеличить объем памяти старого поколения.
Общая настройка сборки мусора требует много усилий и времени, и для этого нет жестких и быстрых правил. Вам нужно будет попробовать различные варианты и сравнить их, чтобы найти лучший, подходящий для вашего приложения.
Это все для модели памяти Java, управления памятью в Java и сбора мусора, я надеюсь, что это поможет вам понять память JVM и процесс сбора мусора.
Основные симптомы утечек памяти Java
Встречаются несколько симптомов указывающих, что приложение имеет проблемы утечки памяти. Небольшое снижение производительности, а не внезапный отказ работы приложения, указывает только на утечку памяти. Проблема может случаться каждый раз во время работы или только тогда, когда приложение начинает работать с большим объемом данных или, напротив, вы начинаете масштабировать приложение. Приложение, возможно, покажет ошибку нехватки памяти, как только утечка сожрет все доступные ресурсы памяти. Перезапустив приложение и надеясь на лучшее, натолкнетесь неоднократно на аварийные завершения до того момента, пока утечка не будет устранена. В общем, утечки памяти случаются когда ссылки на объекты накапливаются, вместо того, чтобы освобождать память. Они занимают всю доступную память и делают для приложения невозможным доступ до нужных ему ресурсов.
Ошибки конфигурации выглядящие как утечки памяти
Перед тем, как заглянете в ситуации вызывающие проблемы с памятью Java и проведете анализ, необходимо убедиться, что исследования не имеют отношения к абсолютно другой задаче. Часть ошибок out-of-memory возникают из-за различных ошибок, например ошибок конфигурации. У приложения, возможно, недостаток памяти в куче или оно конфликтует в системе с другими приложениями. Если начинаете говорить о проблемах нехватки памяти, но не можете определить что вызывает утечку, взгляните на приложение по-другому. Обнаружится, что нужно сделать изменения в потоке финализации или увеличить объем permanent generation пространства, являющегося областью памяти JVM для хранения описания классов Java и некоторых дополнительных данных.
Преимущества инструментов мониторинга памяти
Инструменты мониторинга памяти дают бОльшую видимость использования доступных ресурсов приложением Java. Используя данное ПО, вы делаете шаг для сужения поиска корня проблемы утечки памяти и прочих инцидентов связанных с производительностью. Инструменты идут в нескольких категориях, и вам, возможно, нужно использовать множество приложений, чтобы разобраться как начать правильно обозначать проблему и что пошло не так, даже если вы имеете дело с утечками памяти. Heap dump (дампа кучи) файлы дают необходимые сведения для анализа Java-памяти. В этом случае вам нужно использовать два инструмента: один для генерации дамп-файла и другой для подробного анализа. Такое решение дает детализированную информацию о том, что происходит с приложением. Один раз инструмент указывает места возможных проблем и работает над сужением площади, чтобы обнаружить точное место возникновения инцидента. И этот период времени - время самой длинной и портящей настроение части проб и ошибок. Анализатор памяти указывает несколько проблем в коде, но вы не уверены абсолютно, с какими проблемами столкнулось ваше приложение. Если всё ещё сталкиваетесь с прежней ошибкой, начните сначала и поработайте над другой возможной ошибкой. Сделайте одно изменение за раз и попытайтесь продублировать ошибку. Нужно будет дать приложению поработать некоторое время, чтобы продублировать условия возникновения ошибки. Если при первом тесте происходит утечка памяти, не забудьте протестировать приложение под нагрузкой. Приложение может работать отлично с небольшим количеством данных, но может снова выбросить прежние ошибки при работе с большим объемом данных. Если еще возникает всё та же самая ошибка, нужно начать сначала и разобрать другую возможную причину. Инструменты мониторинга памяти доказывают свою пользу после того, когда приложение стало полностью работающим. Можно удаленно наблюдать за производительностью JVM и проактивным обнаружением сбойных ситуаций перед тем, как разработчик погрузится в проблему и будет собирать исторические данные производительности, чтобы помочь себе в будущем улучшить техники программирования и наблюдать как Java работает под тяжелой нагрузкой. Многие решения включают режимы оповещения "опасность" или другие подобные режимы и разработчик сразу может знать, что происходит не так, как хотелось. Каждый разработчик не хочет, чтобы критическое приложение, будучи в промэксплуатации, падало и являлось причиной потери десятков или сотен тысяч долларов во время простоя приложения, поэтому инструменты мониторинга памяти уменьшают время реагирования разработчика. Приложения мониторинга памяти дают начать процесс диагностики мгновенно, вместо того, чтобы попросить вас пойти к заказчику, где никто не скажет какая именно ошибка случилось или какой код ошибки выдало приложение. Если часто погружаетесь в проблемы памяти и производительности вашего Java-приложения, плотно возьмитесь за процесс тестирования. Обозначьте каждую слабую область в процессе разработки и измените стратегии тестирования. Посоветуйтесь с коллегами и сравните свои подходы тестирования с существующими лучшими практиками. Иногда вам надо пересмотреть маленький фрагмент кода и далее обеспечить длительное воздействие на все приложение.
Роль Garbage Collector на память Java и утечки памяти
Garbage Collector (cборщик мусора) в Java играет ключевую роль в производительности приложения и использования памяти. Он ищет неиспользуемые (мертвые) объекты и удаляет их. Эти объекты больше не занимают память, так что ваше приложение продолжает обеспечивать доступность ресурсов. Иногда приложение не дает GC достаточно времени или ресурсов для удаления мертвых объектов и они накапливаются. Можно столкнуться с такой ситуацией когда идет активное обращение к объектам, которые, вы полагаете, мертвы. Сборщик мусора не может сделать ничего c этим, т.к. его механизм автоматизированного управления памяти обходит активные объекты. Обычно сборщик мусора работает автономно, но необходимо настроить его поведение на реагирование тяжелых проблем с памятью. Однако, GC может сам приводить к проблемам производительности.
Области GC
Сборщик мусора для оптимизации сборки разделяет объекты на разные области. В Young Generation представлены объекты, которые отмирают быстро. Сборщик мусора часто работает в этой области, с того момента, когда он должен проводить очистку. Объекты оставшиеся живыми по достижению определенного периода переходят в Old Generation. В области Old Generation объекты остаются долгое время, и они не удаляются сборщиком так часто. Однако, когда сборщик работает в области, приложение проходит проходит через большую операцию, где сборщик смотрит сквозь живые объекты для очистки мусора. В итоге объекты приложения находятся в конечной области permanent generation. Обычно, эти объекты включают нужные метаданные JVM. Приложение не генерирует много мусора в Permanent Generation, но нуждается в сборщике для удаления классов когда классы больше не нужны.
Связь между Garbage Collector и временем отклика
Сборщик мусора, независимо от приоритета исполнения потоков приложения, останавливает их не дожидаясь завершения. Такое явление называется событием "Stop the World". Область Young Generation сборщика мусора незначительно влияет на производительность, но проблемы заметны, если GC выполняет интенсивную очистку. В конечном итоге вы оказываетесь в ситуации, когда минорная сборка мусора Young Generation постоянно запущена или Old Generation переходит в неконтролируемое состояние. В такой ситуации нужно сбалансировать частоту Young Generation с производительностью, которая требует увеличение размера этой области сборщика. Области Permanent Generation и Old Generation сборщика мусора значительно влияют на производительность приложения и использования памяти. Эта операция major очистки мусора проходит сквозь heap, чтобы вытолкнуть отмершие объекты. Процесс длится дольше чем minor сборка и влияние на производительность может идти дольше. Когда высокая интенсивность очистки и большой размер области Old Generation, производительность всего приложения увязывает из-за событий "Stop the world". Оптимизация сборки мусора требует мониторинга как часто программа запущена, влияния на всю производительность и способов настройки параметров приложения для уменьшения частоты мониторинга. Возможно нужно будет идентифицировать один и тот же объект, размещенный больше, чем один раз, причем приложению не нужно отгораживаться от размещения или вам надо найти точки сжатия, сдерживающие всю систему. Получение правильного баланса требует уделения близкого внимания ко всему от нагрузки на CPU до циклов вашего сборщика мусора, особенно если Young и Old Generation несбалансированы. Адресация утечек памяти и оптимизация сборки мусора помогает увеличить производительность Java-приложения. Вы буквально жонглируете множеством движущихся частей. Но с правильным подходом устранения проблем и инструментами анализа, спроектированных чтобы дать строгую видимость, вы достигнете света в конце туннеля. В противном случае замучаетесь с возникающими неполадками связанных с произодительностью. Тщательное размещение памяти и её мониторинг играют критическую роль в Java-приложении. Необходимо полностью взять в свои руки взаимодействие между сборкой мусора, удалением объектов и производительностью, чтобы оптимизировать приложение и избежать ошибок упирающихся в нехватку памяти. Инструменты мониторинга дают оставаться на высоте, чтобы обнаружить возможные проблемы и обозначить тенденции утилизации памяти так, что вы принимаете проактивный подход к исправлению неисправностей. Утечки памяти часто показывают неэффективность устранения неисправностей обычным путем, особенно если вы сталкиваетесь с неверными значениями параметров конфигурации, но решения вопросов связанных с памятью помогают быстро избежать инцидентов стоящих у вас на пути. Совершенство настройки памяти Java и GC делают ваш процесс разработки намного легче.
Управление памятью - это процесс размещения новых объектов и удаление неиспользуемых объектов, чтобы освободить место для этих новых ассигнований объектов. Традиционным для языков программирование способом управления памятью является ручной. Его сущность является в следующем:
- Для создания объекта в динамической памяти программист явно вызывает команду выделения памяти. Эта команда возвращает указатель на выделенную область памяти, который сохраняется и используется для доступа к ней.
- До тех пор, пока созданный объект нужен для работы программы, программа обращается к нему через ранее сохранённый указатель.
- Когда надобность в объекте проходит, программист явно вызывает команду освобождения памяти, передавая ей указатель на удаляемый объект.
- Ручное управление памятью допускает потенциально возможные две проблемы: висячие ссылки и утечки памяти.
Висячая ссылка — это оставшаяся в использовании ссылка на объект, который уже удалён. После удаления объекта все сохранившиеся в программе ссылки на него становятся «висячими». Память, занимаемая ранее объектом, может быть передана операционной системе и стать недоступной, или быть использована для размещения нового объекта в той же программе.
Утечка памяти - процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих ненужные уже участки памяти.
Все проблемы ручного способа управления памяти в Java решает автоматический сборщик мусора. Но пред ознакомлением со сборщиком мусора, нужно разъяснить понятие кучи(heap).
В Java все объекты находятся в области памяти под названием куча. Куча создается, когда JVM запускается и может увеличиваться или уменьшаться в размерах во время выполнения приложения. Когда куча становится полной, происходит механизм сборки мусора. Все объекты, которые никогда больше не будут использоватся, очищаются. тем самым освобождая место для новых объектов.
Также нужно обратить внимание, что JVM использует больше памяти, чем занимает куча. Например, для методов Java и стеков потоков выделяется память отдельно от кучи.
Размер кучи зависит от используемой платформы, но, как правило, это где-то между 2 и 128 Кб.
Garbage Collection
Механизм сборки мусора - это процесс освобождения места в куче, для возможности добавления новых объектов.
Объекты создаются посредством оператора new, тем самым присваивая объекту ссылку. Закончив работу с объектом, вы просто перестаете на него ссылаться — достаточно присвоить переменной ссылку на другой объект или значение null либо прекратить выполнение метода, чтобы его локальные переменные завершили свое существование естественным образом. Объекты, ссылки на которые отсутствуют, принято называть мусором (garbage), который будет удален.
Виртуальная машина Java, применяя механизм сборки мусора, гарантирует, что любой объект, обладающий ссылками, остается в памяти — все объекты, которые недостижимы из выполняемого кода ввиду отсутствия ссылок на них, удаляются с высвобождением отведенной для них памяти. Точнее говоря, объект не попадает в сферу действия процесса сборки мусора, если он достижим посредством цепочки ссылок, начиная с корневой (root) ссылки, т.е. ссылки, непосредственно существующей в выполняемом коде.
Память освобождается сборщиком мусора по его собственному "усмотрению" и обычно только в тех случаях, когда для дальнейшей работы программы необходим фрагмент свободной памяти большего размера, нежели тот, который имеется в распоряжении виртуальной машины в данный момент, либо если сборщик мусора "предвидит" потенциальную нехватку памяти в ближайшем будущем. Программа может (часто именно так и происходит) завершить работу, не исчерпав ресурсов свободной памяти или даже не приблизившись к этой черте, и поэтому ей так и не потребуются "услуги" сборщика мусора. Объект считается "более недостижимым", если ни одна из переменных в коде, выполняемом в данный момент, не содержит ссылок на него либо цепочка ссылок, которая могла бы связать объект с некоторой переменной программы, обрывается.
Мусор собирается системой без вашего вмешательства, но это не значит, что процесс не требует внимания вовсе. Необходимость создания и удаления большого количества объектов существенным образом сказывается на производительности приложений, и если быстродействие программы является важным фактором, следует тщательно обдумывать решения, связанные с созданием объектов, — это, в свою очередь, уменьшит и объем мусора, подлежащего утилизации.
Читайте также: