Как работает память в Java

Обновлено: 03.07.2024

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

1. Факторы эффективности

Важные факторы, влияющие на производительность программы Java, можно разделить на две основные части:

Потребление памяти программой Java

Общее время выполнения программы

В случае, если программа в каком-то случае является программой, которая взаимодействует с другими, время отклика также является очень важным фактором производительности программы.

Средний ЦП может выполнять примерно 1 миллиард (10^9) операций в секунду.

В этой статье параллелизм не рассматривается. Если вы хотите прочитать о параллелизме/многопоточности, см. статью Параллелизм/многопоточность в Java

2. Обработка памяти в Java

Java использует две области памяти. куча и стек. Мы начнем с краткого обзора памяти в целом на компьютере. Затем объясняются куча и стек Java.

2.1. Собственная память

Собственная память — это память, доступная для процесса, например. Java-процесс. Собственная память управляется операционной системой (ОС) и основана на физической памяти и других физических устройствах, например. диски, флэш-память и т. д.

Процессор (ЦП) компьютера вычисляет инструкции для выполнения и сохраняет результаты вычислений в регистрах. Эти регистры представляют собой элементы быстрой памяти, в которых хранится результат ЦП. Процессор может получить доступ к обычной памяти через шину памяти. Объем памяти, к которому может получить доступ ЦП, зависит от размера физического адреса, который ЦП использует для идентификации физической памяти. 16-битный адрес может получить доступ к 2^16 (= 65,536) ячейкам памяти. 32-битный адрес может получить доступ к 2^32 (= 4.294.967.296) ячейкам памяти. Если каждая область памяти состоит из 8 байтов, то 16-разрядная система может получить доступ к 64 КБ памяти, а 32-разрядная система может получить доступ к 4 ГБ памяти.

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

Текущие 32-разрядные системы используют расширение (Physical Address Extension (PAE)), которое расширяет физическое пространство до 36-разрядной операционной системы. Это позволяет ОС получить доступ к 64 ГБ. Затем ОС использует виртуальную память, чтобы предоставить отдельному процессу 4 ГБ памяти. Даже при включенном PAE процесс не может получить доступ к более чем 4 ГБ памяти.

Конечно, с 64-разрядной ОС это ограничение в 4 ГБ больше не существует.

2.2. Память в Java

Java управляет используемой памятью. Новые объекты создаются и помещаются в кучу. Как только ваше приложение больше не ссылается на объект, сборщик мусора Java может удалить этот объект и освободить память, чтобы ваше приложение могло снова использовать эту память.

2.3. Куча Java

В куче виртуальная машина Java (JVM) хранит все объекты, созданные приложением Java, например с помощью оператора «новый». Сборщик мусора Java (gc) может логически разделить кучу на разные области, чтобы сборщик мусора мог быстрее идентифицировать объекты, которые можно удалить.

2.4. Стек Java

Стек — это место, где хранятся вызовы методов и локальные переменные. Если вызывается метод, то его кадр стека помещается на вершину стека вызовов. Фрейм стека содержит состояние метода, включая выполняемую строку кода и значения всех локальных переменных. Метод на вершине стека всегда является текущим выполняемым методом для этого стека. У потоков есть собственный стек вызовов.

2.5. Уклонение от анализа

Как было сказано ранее, объекты Java создаются и хранятся в куче. Язык программирования не дает возможности позволить программисту решить, следует ли генерировать объект в стеке. Но в некоторых случаях было бы желательно разместить объект в стеке, так как выделение памяти в стеке дешевле, чем выделение памяти в куче, освобождение памяти в стеке бесплатно, а среда выполнения эффективно управляет стеком.< /p>

Поэтому JVM использует внутренний escape-анализ, чтобы проверить, используется ли объект только с потоком или методом. Если JVM обнаружит это, она может решить создать объект в стеке, повысив производительность программы Java.

2.6. Утечки памяти

Сборщик мусора JVM освобождает объекты Java из памяти до тех пор, пока на этот объект не ссылается другой объект. Если другие объекты все еще содержат ссылки на эти объекты, то сборщик мусора JVM не может их освободить.

3.Сборщик мусора

JVM автоматически повторно собирает память, которая больше не используется. Память для объектов, на которые больше не ссылаются, будет автоматически освобождена сборщиком мусора. Чтобы убедиться, что сборщик мусора начинает работать, добавьте в виртуальную машину аргумент командной строки "-verbose:gc".

4. Параметры памяти для виртуальной машины Java

JVM работает с фиксированной доступной памятью. Как только эта память будет превышена, вы получите «java.lang.OutOfMemoryError». JVM пытается сделать разумный выбор доступной памяти при запуске (подробности см. в настройках Java), но вы можете заменить значение по умолчанию следующими параметрами.

Чтобы повысить производительность, вы можете использовать определенные параметры в JVM.

Установите минимальный доступный объем памяти для JVM на 1024 МБ

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

Увеличьте значения этих параметров, чтобы избежать следующей ошибки: «Исключение в потоке java.lang.OutOfMemoryError: пространство кучи Java». Обратите внимание, что вы не можете выделить больше памяти, чем доступно физически.

Если вы запускаете программу на Java из командной строки, используйте, например, следующую настройку: java -Xmx1024m YourProgram. В Eclipse вы можете использовать аргументы виртуальной машины в конфигурации запуска.

5. Потребление памяти и время выполнения

Обычно операция считается обширной, если она требует длительного времени выполнения или большого потребления памяти.

5.1. Потребление памяти

5.2. Время выполнения Java-программы

Используйте System.currentTimeMillis(), чтобы получить время начала и время окончания и вычислить разницу.

6. Ленивая инициализация

Если создание переменной обходится очень дорого, иногда полезно отложить создание этой переменной до тех пор, пока она не понадобится. Это называется ленивой инициализацией. В общем случае ленивую инициализацию следует использовать только в том случае, если анализ показал, что это действительно очень затратная операция. Это основано на том факте, что ленивая инициализация затрудняет чтение кода. Я использую проект "de.vogella.performance.lazyinitialization" для примеров в этой главе. И иметь собственное поле.

6.1. Параллелизм — обзор

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

7. JIT-компилятор

JIT-компилятор Java компилирует байт-код Java в собственный исполняемый код во время выполнения вашей программы. Это значительно увеличивает время выполнения вашей программы. Компилятор JIT использует информацию о времени выполнения для определения частей вашего приложения, которые требуют интенсивного выполнения. Эти так называемые «горячие точки» затем переводятся в собственный код. По этой причине компилятор JIT также называют компилятором «горячих точек». JIT сохраняет исходный байт-код и собственный код в памяти, потому что JIT также может решить, что определенные шаги компиляции должны быть пересмотрены.

8. Использование VisualVM (jvisualvm)

8.1. Что такое VisualVM?

jvisualvm – это инструмент для анализа поведения вашего Java-приложения во время выполнения. Это позволяет вам отслеживать работающую программу Java и видеть ее потребление памяти и процессора. Его также можно использовать для создания дампа кучи памяти для анализа объектов в куче.

8.2. Создание дампа кучи с помощью

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

9. Нагрузочный тест

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

В этой статье основное внимание будет уделено управлению памятью в Java, работе кучи, ссылочным типам, сборке мусора и связанным с ними понятиям.

Зачем изучать управление памятью в Java?
Все мы знаем, что Java сама управляет памятью и не требует явного вмешательства программиста. Сам сборщик мусора обеспечивает очистку неиспользуемого пространства и освобождение памяти, когда она не нужна. Итак, какова роль программиста и почему программист должен знать об управлении памятью Java? Будучи программистом, вам не нужно заморачиваться такими проблемами, как уничтожение объектов, все заслуги перед сборщиком мусора. Однако автоматическая сборка мусора не гарантирует всего. Если мы не знаем, как работает управление памятью, часто мы оказываемся среди вещей, которыми не управляет JVM (виртуальная машина Java). Некоторые объекты не подлежат автоматической сборке мусора.

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

Введение:

В каждом языке программирования память является жизненно важным ресурсом, которого также не хватает в природе. Следовательно, важно, чтобы память управлялась тщательно без каких-либо утечек. Выделение и освобождение памяти является важной задачей и требует большой осторожности и внимания. Однако в Java, в отличие от других языков программирования, JVM и, если быть точным, сборщик мусора выполняют роль управления распределением памяти, так что программисту это не нужно. В то время как в других языках программирования, таких как C, программист имеет прямой доступ к памяти, которая выделяет память в своем коде, тем самым создавая большие возможности для утечек.

Основные концепции управления памятью Java:

  • Структура памяти JVM
  • Работа сборщика мусора

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

Части области памяти JVM

Части области памяти JVM

Давайте подробно изучим эти части области памяти:

  • Это общая область данных времени выполнения, в которой фактический объект хранится в памяти. Он создается во время запуска виртуальной машины.
  • Эта память выделяется для всех экземпляров класса и массива. Куча может иметь фиксированный или динамический размер в зависимости от конфигурации системы.
  • JVM предоставляет пользователю возможность инициализировать или изменять размер кучи в соответствии с требованиями. Когда используется новое ключевое слово, объекту назначается место в куче, но ссылка на него существует в стеке.
  • Для работающего процесса JVM существует одна и только одна куча.

Приведенный выше оператор создает объект класса Scanner, который размещается в куче, тогда как ссылка «sc» помещается в стек.

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

Область метода:

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

Примечание. Хотя область метода логически является частью кучи, она может быть или не быть сборщиком мусора, даже если сборка мусора в области кучи является обязательной.

Стеки JVM:

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

Стеки собственных методов:

Нативные стеки методов, также называемые стеками C, написаны не на языке Java. Эта память выделяется для каждого потока при его создании. И он может быть фиксированным или динамическим.

Регистры счетчика программ (ПК):

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

Работа сборщика мусора:

  • JVM запускает этот процесс, и в соответствии с процессом сборки мусора JVM выполняется или задерживается. Это снижает нагрузку на программиста, автоматически выполняя выделение или освобождение памяти.
  • Процесс сбора мусора приводит к приостановке остальных процессов или потоков и, таким образом, является дорогостоящим по своей природе. Эта проблема неприемлема для клиента, но ее можно устранить, применив несколько алгоритмов, основанных на сборщике мусора. Этот процесс применения алгоритма часто называют настройкой сборщика мусора, и он важен для повышения производительности программы.
  • Еще одно решение — это сборщики мусора поколений, которые добавляют поле возраста к объектам, которым назначена память.По мере того, как создается все больше и больше объектов, список мусора растет, тем самым увеличивая время сборки мусора. В зависимости от того, сколько тактов пережили объекты, объекты группируются и соответственно им присваивается «возраст». Таким образом распределяется работа по сборке мусора.
  • В текущем сценарии все сборщики мусора относятся к поколениям и, следовательно, являются оптимальными.

Примечание: System.gc() и Runtime.gc() — это методы, которые явно запрашивают сборку мусора в JVM, но не гарантируют сборку мусора, поскольку окончательное решение о сборке мусора принимается только JVM.

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

Присоединяйтесь к сообществу 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, предоставив различные конфигурации, наиболее подходящие для вашего работающего приложения. Выявить и устранить утечку памяти очень просто, если использовать правильные инструменты.

Стек в Java — это раздел памяти, содержащий методы, локальные переменные и ссылочные переменные. Память стека всегда используется в порядке «последний пришел – первый обслужен». Локальные переменные создаются в стеке.

Что такое динамическая память?

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

Распределение памяти в Java

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

Распределение памяти Java разделено на следующие разделы:

Это разделение памяти необходимо для ее эффективного управления.

  • Раздел кода содержит ваш байт-код.
  • Раздел памяти Stack содержит методы, локальные переменные и ссылочные переменные.
  • Раздел Heap содержит объекты (также может содержать ссылочные переменные).
  • Статический раздел содержит статические данные/методы.

Разница между локальной переменной и переменной экземпляра

Переменная экземпляра объявляется внутри класса, но не внутри метода

Локальная переменная объявляется внутри метода, включая аргументы метода.

Разница между стеком и кучей

Нажмите здесь, если видео недоступно

Давайте рассмотрим пример, чтобы лучше понять это.

Учтите, что ваш основной метод вызывает метод m1

В стеке java кадр будет создан из метода m1.

Java Stack and Heap

Переменная X в m1 также будет создана во фрейме для m1 в стеке. (См. изображение ниже).

Java Stack and Heap

Метод m1 вызывает метод m2. В стеке java создается новый фрейм для m2 поверх фрейма m1.

Java Stack and Heap

Java Stack and Heap

Переменные b и c также будут созданы во фрейме m2 в стеке.

Java Stack and Heap

Тот же метод m2 вызывает метод m3. Снова наверху стека создается кадр m3 (см. изображение ниже).

Стек и куча Java

Java Stack and Heap

Теперь предположим, что наш метод m3 создает объект для класса «Учетная запись», который имеет две переменные экземпляра: int p и int q.

Вот код метода m3

Выражение new Account() создаст объект account в куче.

Java Stack and Heap

Ссылочная переменная «ref» будет создана в стеке java.

Java Stack and Heap

Операция присваивания «=» создаст ссылочную переменную, указывающую на объект в куче.

Java Stack and Heap

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

Java Stack and Heap

Стек из метода m3 будет очищен.

Java Stack and Heap

Поскольку ссылочная переменная больше не будет указывать на объект в куче, она будет допущена к сборке мусора.

Java Stack and Heap

После завершения выполнения метода m2. Он будет удален из стека, а все его переменные будут сброшены и больше не будут доступны для использования.

Аналогично для метода m1.

В конечном итоге поток управления вернется к начальной точке программы. Обычно это «основной» метод.

Что, если Object имеет ссылку в качестве переменной экземпляра?

В этом случае ссылочная переменная «child» будет создана в куче, которая, в свою очередь, будет указывать на свой объект, что-то вроде схемы, показанной ниже.

Читайте также: