Использование виртуальной памяти из Java под Linux, слишком много использованной памяти

У меня проблема с Java-приложением, работающим под Linux.

Когда я запускаю приложение, используя максимальный размер кучи по умолчанию (64 МБ), я вижу, что приложение tops использует 240 МБ виртуальной памяти для приложения. Это создает некоторые проблемы с некоторым другим программным обеспечением на компьютере, которое относительно ограничено ресурсами.

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

В любом случае я могу настроить виртуальную память, используемую для процесса Java в Linux?

Изменить 1. Проблема не в куче. Проблема в том, что если я установил кучу 128 МБ, то, по-прежнему, Linux выделяет 210 МБ виртуальной памяти, которая не нужна никогда. **

Изменить 2. Использование ulimit -v позволяет ограничить объем виртуальной памяти. Если размер установлен ниже 204 МБ, то приложение не будет работать, даже если ему не требуется 204 МБ, всего 64 МБ. Поэтому я хочу понять, почему Java требует столько виртуальной памяти. Можно ли это изменить?

Изменить 3. В системе есть несколько других приложений, которые встроены. И система имеет ограничение виртуальной памяти (от комментариев, важных деталей).

Ответ 1

Это давняя жалоба на Java, но она в значительной степени бессмысленна и обычно основана на поиске неправильной информации. Обычная фразировка - это что-то вроде "Hello World на Java занимает 10 мегабайт! Зачем это нужно?" Ну, вот способ сделать Hello World на 64-битной JVM-заявке взять на себя 4 гигабайта... хотя бы по одной форме измерения.

java -Xms1024m -Xmx4096m com.example.Hello

Различные способы измерения памяти

В Linux команда top дает вам несколько разных номеров для памяти. Вот что он говорит о примере Hello World:

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2120 kgregory  20   0 4373m  15m 7152 S    0  0.2   0:00.10 java
  • VIRT - это пространство виртуальной памяти: сумма всего на карте виртуальной памяти (см. ниже). Это в значительной степени бессмысленно, за исключением случаев, когда это не так (см. Ниже).
  • RES - это размер резидентного набора: количество страниц, которые в настоящее время находятся в ОЗУ. Почти во всех случаях это единственный номер, который вы должны использовать, говоря "слишком большой". Но это все еще не очень хорошее число, особенно когда речь идет о Java.
  • SHR - это количество резидентной памяти, которая совместно используется другими процессами. Для процесса Java это обычно ограничивается разделяемыми библиотеками и JAR файлами с отображением памяти. В этом примере у меня был только один процесс Java, поэтому я подозреваю, что 7k является результатом библиотек, используемых ОС.
  • SWAP не включен по умолчанию и здесь не отображается. Он указывает объем виртуальной памяти, которая в настоящее время находится на диске, независимо от того, действительно ли она находится в области подкачки. ОС очень хорошо хранит активные страницы в ОЗУ, и единственное средство для обмена - (1) купить больше памяти или (2) уменьшить количество процессов, поэтому лучше всего игнорировать это число.

Ситуация для диспетчера задач Windows немного сложнее. В Windows XP имеются столбцы "Использование памяти" и "Размер виртуальной памяти", но официальная документация не говорит о том, что они означают. Windows Vista и Windows 7 добавляют больше столбцов, и они фактически документированы. Из них измерение "Рабочий набор" является наиболее полезным; это примерно соответствует сумме RES и SHR в Linux.

Общие сведения о карте виртуальной памяти

Виртуальная память, потребляемая процессом, является суммой всего, что находится на карте памяти процесса. Сюда входят данные (например, куча Java), но также и все разделяемые библиотеки и файлы с отображением памяти, используемые программой. В Linux вы можете использовать команду pmap, чтобы увидеть все вещи, отображаемые в пространстве процесса (отсюда я только собираюсь обратиться к Linux, потому что это то, что я использую, я уверен, что для Windows есть эквивалентные инструменты). Здесь выдержка из карты памяти программы "Hello World"; вся карта памяти имеет длину более 100 строк, и нет ничего необычного в списке тысяч строк.

0000000040000000     36K r-x--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000      8K rwx--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000    676K rwx--    [ anon ]
00000006fae00000  21248K rwx--    [ anon ]
00000006fc2c0000  62720K rwx--    [ anon ]
0000000700000000 699072K rwx--    [ anon ]
000000072aab0000 2097152K rwx--    [ anon ]
00000007aaab0000 349504K rwx--    [ anon ]
00000007c0000000 1048576K rwx--    [ anon ]
...
00007fa1ed00d000   1652K r-xs-  /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000   1024K rwx--    [ anon ]
00007fa1ed2d3000      4K -----    [ anon ]
00007fa1ed2d4000   1024K rwx--    [ anon ]
00007fa1ed3d4000      4K -----    [ anon ]
...
00007fa1f20d3000    164K r-x--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000   1020K -----  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000     28K rwx--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000     16K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000      4K rwx--  /lib/x86_64-linux-gnu/libc-2.13.so
...

Быстрое объяснение формата: каждая строка начинается с адреса виртуальной памяти сегмента. За этим следует размер сегмента, разрешения и источник сегмента. Этот последний элемент представляет собой либо файл, либо "анон", который указывает блок памяти, выделенный через mmap.

Начиная с вершины, мы имеем

  • Загрузчик JVM (т.е. программа, которая запускается при вводе java). Это очень мало; все, что он делает, это загрузка в разделяемых библиотеках, где хранится настоящий JVM-код.
  • Куча блоков анонов, содержащих кучу Java и внутренние данные. Это Sun JVM, поэтому куча разбита на несколько поколений, каждый из которых является собственным блоком памяти. Обратите внимание, что JVM выделяет пространство виртуальной памяти на основе значения -Xmx; это позволяет иметь смежную кучу. Значение -Xms используется внутри, чтобы сказать, сколько кучи "используется" при запуске программы, и запускать сборку мусора по мере приближения этого предела.
  • Отображается JAR файл с отображением памяти, в этом случае файл, содержащий "классы JDK". Когда вы карты памяти JAR, вы можете получить доступ к файлам в нем очень эффективно (в отличие от чтения с самого начала каждый раз). Sun JVM будет отображать все JAR-карты на пути к классам; если ваш код приложения должен получить доступ к JAR, вы также можете его отобразить на карте.
  • Данные по потоку для двух потоков. Блок 1М представляет собой стек потоков; Я не знаю, что входит в блок 4K. Для реального приложения вы увидите десятки, если не сотни этих записей повторяются через карту памяти.
  • Одна из разделяемых библиотек, которая содержит фактический код JVM. Есть несколько из них.
  • Общая библиотека для стандартной библиотеки C. Это всего лишь одна из многих вещей, которые загружают JVM, которые не являются строго частью Java.

Особый интерес представляют разделяемые библиотеки: каждая разделяемая библиотека имеет как минимум два сегмента: сегмент, предназначенный только для чтения, содержащий код библиотеки, и сегмент чтения-записи, который содержит глобальные данные для каждого процесса для библиотеки (я не знаете, что такое сегмент без разрешений, я видел его только на x64 Linux). Доступная только для чтения часть библиотеки может быть разделена между всеми процессами, использующими библиотеку; например, libc имеет 1,5 М виртуального пространства памяти, которое может быть общим.

Важно знать размер виртуальной памяти?

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

Если размер виртуальной памяти важен, это если вы работаете в 32-разрядной операционной системе, где вы можете выделять только 2 Гбит (или, в некоторых случаях, 3Gb) адресного пространства процесса. В этом случае вы имеете дело с ограниченным ресурсом и, возможно, придется делать компромиссы, например, уменьшать размер вашей кучи, чтобы карта памяти отображала большой файл или создавала много потоков.

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

Когда задан размер резидентного регистра?

Размер резидентного набора - это часть виртуального пространства памяти, которое фактически находится в ОЗУ. Если ваш RSS станет значительной частью вашей общей физической памяти, возможно, пора начать беспокоиться. Если ваш RSS растет, чтобы охватить всю вашу физическую память, и ваша система начнет меняться, это хорошо прошлое, чтобы начать беспокоиться.

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

Нижняя линия

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

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

Доступ к диску (то есть к базе данных) стоит дорого, а память дешевая. Если вы можете обменять один на другой, сделайте это.

Ответ 2

Известна проблема с Java и glibc >= 2.10 (включает Ubuntu >= 10.04, RHEL >= 6).

Лечение заключается в установке этого env. переменная: export MALLOC_ARENA_MAX=4 Если вы используете Tomcat, вы можете добавить это в файл TOMCAT_HOME/bin/setenv.sh.

Существует статья IBM об установке MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

В этом сообщении в блоге указано

Известно, что резидентная память

ползает по типу, аналогичному утечка памяти или фрагментация памяти.

найдите MALLOC_ARENA_MAX в Google или SO для получения дополнительных ссылок.

Возможно, вы захотите настроить также другие параметры malloc для оптимизации для низкой фрагментации выделенной памяти:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

Ответ 3

Объем памяти, выделенной для процесса Java, в значительной степени зависит от того, что я ожидаю. У меня были аналогичные проблемы с запуском Java на встроенных/ограниченных памяти системах. Запуск любого приложения с произвольными пределами VM или системами, которые не имеют достаточного количества свопов, как правило, ломаются. Похоже, что многие современные приложения не предназначены для использования в системах с ограниченными ресурсами.

У вас есть еще несколько вариантов, которые вы можете попробовать и ограничить объем памяти JVM. Это может уменьшить площадь виртуальной памяти:

-XX: ReservedCodeCacheSize = 32m Размер кэша зарезервированного кода (в байтах) - максимум размер кеша кода. [Solaris 64-бит, amd64 и -server x86: 48m; в 1.5.0_06 и ранее, Solaris 64-bit и and64: 1024m.]

-XX: MaxPermSize = 64 м. Размер постоянного поколения. [5.0 и новее: 64-битные виртуальные машины масштабируются на 30% больше; 1.4 amd64: 96 м; 1.3.1 -client: 32m.]

Кроме того, вы также должны установить свой -Xmx (максимальный размер кучи) как можно ближе к значению фактического пикового использования памяти вашего приложения. Я считаю, что поведение по умолчанию JVM по-прежнему удваивает размер кучи каждый раз, когда он расширяет его до макс. Если вы начнете с кучи 32M, а ваше приложение достигнет максимума до 65M, тогда куча закончится ростом 32M → 64M → 128M.

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

-XX: MinHeapFreeRatio = 40 Минимальный процент свободной кучи после GC избегайте расширения.

Кроме того, из того, что я вспоминаю из экспериментов с этим несколько лет назад, количество загруженных загружаемых библиотек оказало огромное влияние на минимальную площадь. Загрузка java.net.Socket добавила более 15M, если я правильно помню (и, вероятно, не знаю).

Ответ 4

Sun JVM требует большой памяти для HotSpot и отображает ее в библиотеках времени выполнения в общей памяти.

Если проблема с памятью связана с использованием другой JVM, подходящей для встраивания. IBM имеет j9, и есть "jamvm" с открытым исходным кодом, который использует библиотеки классов классов GNU. Также Sun имеет JVM Squeak, работающий на SunSPOTS, поэтому есть альтернативы.

Ответ 5

Просто подумайте, но вы можете проверить влияние a ulimit -v вариант.

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

Ответ 6

Одним из способов уменьшения кучи системы с ограниченными ресурсами может быть игра с переменной -XX: MaxHeapFreeRatio. Обычно это значение равно 70, и это максимальный процент кучи, который свободен до того, как GC сжимает его. Установите его на меньшее значение, и вы увидите, например, профайлер jvisualvm, что для вашей программы обычно используется меньшая куча.

EDIT: для установки небольших значений для -XX: MaxHeapFreeRatio вы также должны установить -XX: MinHeapFreeRatio Например,

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: Добавлен пример для реального приложения, которое запускается и выполняет одну и ту же задачу, с параметрами по умолчанию, и с параметрами 10 и 25. Я не заметил никакой реальной разницы в скорости, хотя теория Java должна использовать больше времени для увеличения кучи в последнем примере.

Default parameters

В конце, максимальная куча 905, используемая куча 378

MinHeap 10, MaxHeap 25

В конце максимальная куча равна 722, используемая куча - 378

На самом деле это немного, поскольку наше приложение работает на удаленном настольном сервере, и многие пользователи могут запускать его сразу.

Ответ 7

Sun java 1.4 имеет следующие аргументы для управления объемом памяти:

-Xmsn     Укажите начальный размер пула распределения памяти в байтах. Это значение должно быть кратно 1024 более 1 МБ. Приложите букву k или K, чтобы указать килобайты, или m или M для указания мегабайт. По умолчанию значение равно 2 МБ. Примеры:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn     Укажите максимальный размер пула распределения памяти в байтах. Это значение должно быть кратно 1024 более 2 МБ. Приложите букву k или K, чтобы указать килобайты, или m или M для указания мегабайт. По умолчанию значение составляет 64 МБ. Примеры:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

В Java 5 и 6 есть еще несколько. См. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

Ответ 8

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

Кроме того, вы можете попробовать другой JVM, затем Sun one, с меньшим объемом памяти, но я не могу посоветовать здесь.