Поиск по сайту:
Плакать — не всегда значит страдать (Фелиция Жанлис).

Как управлять памятью и 5 основных проблем с памятью

06.08.2024
Как управлять памятью и 5 основных проблем с памятью

Управление памятью в Java, или автоматическая очистка, звучит почти идеально. В отличие от C / C ++, вам не нужно вручную освобождать память, выделенную в вашем коде. В Java, когда у объекта больше нет переменных, ссылающихся на него (* ie * количество ссылок = 0), он становится пригодным для сборки мусора. Виртуальная машина Java (JVM) периодически запускает сборщик мусора, чтобы освободить такую память для других целей.

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

Поскольку автоматическая сборка мусора в JVM не гарантирует, что объекты будут переработаны, понимание того, как управлять памятью в Java, имеет решающее значение. Разработчик должен убедиться, что код оптимально обрабатывает объекты и делает их пригодными для сборки мусора, когда они больше не требуются.

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

 

Управление памятью в приложении Java

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

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

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

 

Управление памятью в приложениях Java требует понимания этих различных хранилищ.

 

Stack

Память стека выделяется во время выполнения для каждого потока. В ней хранятся примитивы, переменные, объявленные в методе, и ссылки на объекты в куче. Переменные стека имеют видимость, известную как область этих переменных. Таким образом, переменные, объявленные в методе, недоступны за его пределами. Stack использует подход last in, first out (LIFO) для удаления (pop out) этих переменных при возврате метода. Разработчики должны остерегаться глубоких рекурсивных вызовов функций, которые могут возникнуть, StackOverflowError когда в JVM заканчивается пространство стека.

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

 

Куча

Куча — это наиболее важное хранилище памяти, которое разработчикам Java необходимо учитывать при управлении памятью. Heap является динамическим — он выделяет объекты с помощью ключевого слова new и хранит эти объекты в памяти до тех пор, пока на них есть ссылки в коде. Сборщик мусора использует алгоритм mark-and-sweep для переработки объектов и освобождения памяти кучи. Разработчики должны убедиться, что ссылки на объекты явно назначаются на null, когда они больше не требуются. Аналогичным образом, коллекции (списки, карты и т.д.) Могут значительно увеличиваться при работе с большими данными; следовательно, они должны обрабатываться пакетами, чтобы обеспечить оптимальное использование памяти.

Java допускает необязательные параметры JVM для установки минимального и максимального размеров кучи. Например, -Xms512m и -Xmx1024m укажите минимальный размер кучи в 512 МБ, а максимальный — в 1 ГБ для приложения.

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

 

Metaspace

Metaspace — это хранилище метаданных класса и статических данных, которое заменило старое PermGen начиная с Java 8. Оно выделяется из собственной памяти (вне кучи). Проблемы OutOfMemoryError могут возникать в метапространстве, если некоторые классы загружаются несколько раз. Эти проблемы будут более подробно рассмотрены позже в этой статье.

Читать  V-Color DDR5 R-DIMM обеспечивает скорость 8000 МТ/с на AMD Threadripper

Необязательный параметр JVM MaxMetaspaceSize может использоваться для указания ее размера. Например, -XX:MetaspaceSize=256m задает максимальный размер метапространства в 256 МБ. При отсутствии этого параметра метапространство ограничено самой встроенной памятью.

 

Проблемы с памятью в Java

Вообще говоря, проблемы с памятью возникают из-за плохого управления ресурсами и неадекватного их распределения.

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

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

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

 

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

Утечки памяти происходят из-за непреднамеренных ссылок на объекты, которые ошибочно сохраняются в приложении, даже когда они больше не требуются. Это предотвращает сборку мусора из этих объектов. Со временем случайная утечка увеличивается и занимает значительную часть пространства кучи. Это, в свою очередь, заставляет сборщик мусора часто запускаться, и приложение может аварийно завершать работу, за исключением: OutOfMemoryError.

Серверы приложений Java используют потоки через пулы потоков, поэтому они никогда не собирают мусор. Следовательно, разработчики должны убедиться, что их код должным образом очищает локальные переменные потока (особенно коллекции). Некоторые утечки памяти в коллекциях Java (реализации map, такие как HashMap и Hashtable) также могут возникать из-за неправильной реализации методов equals/hashCode в коде приложения. Контракт между этими двумя методами определяет, что если два объекта равны, они должны выдавать один и тот же хэш-код. Однако обратное неверно. Любая реализация интерфейса карты (скажем, HashMap) использует хэш-код в качестве ключа для поиска объекта. Когда метод hashCode() явно не реализован или если он неправильно реализован в коде, код не сможет найти объект (ы) в HashMap. Более того, код будет продолжать добавлять новые объекты вместо перезаписи старого объекта, что приведет к утечке памяти. Чтобы избежать этого, разработчики должны использовать модульные тесты для контракта equals, чтобы убедиться, что методы equals/hashCode реализованы правильно.

 

Высокое использование памяти

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

Следующий фрагмент кода иллюстрирует высокое использование памяти:

public class Main { 

	public static void main(String[] args) throws Exception { 
  	  List users = new ArrayList<>(); 
  	  for (int i = 0; i < 25000000; i++) { 
  		  users.add(new User("user-" + i, "address-" + i)); 
  	  } 
  	  System.out.println("Created " + users.size()/1000000 + "M users."); 
	} 
} 
class User { 
	String name; 
	String address; 

	public User(String _name, String _address){ 
  	  name = _name; 
  	  address = _address; 
	} 
} 
============== OUTPUT ================ 
java.lang.OutOfMemoryError: Java heap space 
Dumping heap to java_pid1056.hprof ... 
Heap dump file created [2906668209 bytes in 25.721 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

 

Предыдущий код добавляет объекты User в an ArrayList в цикле на 25 миллионов итераций. Затем код завершает работу с an OutOfMemoryError в куче.

 

Проблемы с загрузчиком классов, связанные с памятью

Приложение Java может столкнуться с OutOfMemoryError в метапространстве при утечке памяти в загрузчике классов. Эта проблема с памятью значительно отличается от часто встречающихся проблем с памятью у объектов Java в куче. На классы ссылается их загрузчик классов. Они не являются сборщиком мусора, если сам загрузчик классов не является сборщиком мусора, что происходит, когда сервер приложений выгружает приложение.

Читать  Gigabyte объявляет о поддержке DDR5-8333 и DDR5-9300 OC на некоторых материнских платах Aorus

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

Иногда эта проблема также может возникать, если размер, указанный параметром JVM, MaxMetaspaceSize ниже требований приложения. Это можно легко исправить, указав соответствующий размер для этого параметра.

 

Проблемы с памятью из-за большой обработки JSON / XML

В современных приложениях Java потоки JSON или XML часто анализируются для заполнения данных в коллекциях, базах данных и других объектах. Если код приложения использует только относительно небольшие файлы / потоки JSON / XML во время разработки, он может столкнуться с проблемами памяти в рабочей среде, когда эти потоки будут больше. Это тем более справедливо для блоков JSON / XML, которые автоматически генерируются и отправляются через Apache Kafka, RabbitMQ, или другие.

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

Однако в некоторых случаях может потребоваться изменить сам анализатор. Анализатор объектной модели документа (DOM), например, считывает весь фрагмент XML в памяти. Это полезно для XML-данных малого и среднего размера, но потребляет больше памяти для данных большего размера. Для сравнения, анализатор Simple API for XML (SAX) использует событийный последовательный подход к обработке XML-данных и потребляет меньше памяти. Таким образом, в зависимости от требований приложения, часть кода должна быть переработана для использования анализатора SAX вместо анализатора DOM для эффективной обработки больших потоков XML.

 

Проблемы с памятью, связанные с финализаторами

Некоторый код (устаревший или иной) в приложении Java все еще может использовать метод finalize() (устаревший с Java 9) для выполнения работ по очистке, таких как освобождение ресурсов, закрытие дескрипторов файлов и закрытие соединений с БД. JVM вызывает метод finalize() для всех подходящих объектов, прежде чем они будут собраны в мусор, и занятое ими пространство возвращается в память кучи.

Однако сам процесс очистки может занять больше времени. Если имеется большое количество подходящих объектов с трудоемкими реализациями finalize(), они будут помечены и поставлены в очередь на доработку на длительное время. В этом случае JVM может выйти из строя с помощью OutOfMemoryError.

Поскольку нет никакой гарантии, когда или даже если финализация будет выполняться вообще, лучший подход — полностью избегать реализаций finalize(). Такие реализации делают систему хрупкой, и такой код должен быть тщательно переработан архитектором системы. Одним из подходов могло бы быть использование интерфейса  AutoCloseable для таких объектов с помощью инструкции try-with-resources:

class AutoCloseResource implements AutoCloseable { 
    // other code 

    @Override 
    public void close() { 
   	 // closure/clean-up code 
    } 
} 

public void useAutoCloseResource(){ 
 // AutoCloseResource.close() will be called on try-with-resources completion 
   	 System.out.println("Using try-with-resources block to check AutoCloseable"); 
   	 // use AutoCloseResource 
    } 
}

 

Читать  MSI и Patriot объединяются для создания невероятно быстрой оперативной памяти DDR5

Диагностика проблем с памятью с помощью дампов кучи

Несмотря на все меры предосторожности и наилучшие подходы, приложение Java может столкнуться с OutOfMemoryError, и вам может потребоваться выяснить его первопричину (причины).

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

 

Сбор дампа кучи

Вы можете собрать дамп кучи с помощью любого из следующих подходов или инструментов:

  • Параметр JVM: Дампы кучи можно получить, передав этот параметр в JVM: -XX:+HeapDumpOnOutOfMemoryError. Дополнительный параметр JVM позволяет указать путь к файлу для дампа кучи: -XX:HeapDumpPath=path. Это предпочтительный вариант в производственной среде, поскольку он не требует накладных расходов и может помочь устранять проблемы с утечкой памяти по мере их возникновения.
  • jmap: Это инструмент командной строки Java для получения дампа кучи по идентификатору процесса. Использование: jmap -dump:live,format=b,file=<file-name> <pid>.
  • jcmd: Это еще один инструмент командной строки Java. Использование: jcmd GC.heap_dump filename=<file-name>.
  • JConsole: Это инструмент с графическим интерфейсом Java; в отличие от jhat, этот по-прежнему поставляется с JDK-9 и более поздними версиями. Это может быть вызвано как bin/jconsole из командной строки, и вы можете подключить его к процессу приложения Java, указав его PID. Дамп кучи можно получить на вкладке MBeans в разделе HotSpotDiagnostic — dumpHeap.

 

На следующем рисунке показан JConsole с использованием памяти кучи. Посмотрите, как растет использование памяти кучи по мере того, как User объекты добавляются в список в цикле (см. Предыдущий фрагмент кода):

Рис. 1. JConsole: использование памяти в куче

Рис. 1. JConsole: использование памяти в куче

 

Анализ дампа кучи

Как только вы получите дамп кучи (используя любой из перечисленных ранее подходов), его можно проанализировать с помощью бесплатного инструмента, такого как Eclipse Memory Analyzer, для устранения проблем с утечкой памяти в вашем Java-приложении. Диаграммы в этом разделе основаны на примере кода, использованном в разделе «Высокое использование памяти».

Круговая диаграмма: На следующем рисунке показан обзор анализатора памяти Eclipse при открытии файла дампа кучи. Как вы можете видеть, один объект (список users) на этой круговой диаграмме занимает почти весь круг. Вы можете составить список объектов с исходящими ссылками на этой вкладке:

Рис. 2. Анализатор памяти Eclipse: круговая диаграмма

Рис. 2. Анализатор памяти Eclipse: круговая диаграмма

 

Список объектов: На этом втором изображении анализатора памяти Eclipse показан список объектов, занимающих максимальное пространство в куче, что в конечном итоге приводит к OutOfMemoryError. Это ArrayList из объектов User. То есть список пользователей (Java Collection), который заполняется в цикле во фрагменте кода ранее. Вот как вы можете определить основную причину утечки памяти из дампа кучи, используя анализатор памяти:

Рис. 3. Анализатор памяти Eclipse: список объектов

Рис. 3. Анализатор памяти Eclipse: список объектов

 

Заключение

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

В статье рассмотрены пять основных проблем с памятью в приложениях Java, их вероятные причины и способы их устранения.

Наконец, в статье рассматриваются дампы кучи и способы изучения диагностических данных для устранения проблем с памятью. В ней обсуждается пример анализа дампа кучи для обнаружения основной причины утечки памяти и связанных с ней OutOfMemoryError.

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 5,00 из 5)
Загрузка...
Поделиться в соц. сетях:


0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

**ссылки nofollow

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

Это может быть вам интересно


Рекомендуемое
Многие думают, что прокси нужны только продвинутым пользователям или компаниям.…

Спасибо!

Теперь редакторы в курсе.