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

StackOverflow https://stackoverflow.com/questions/561245

Вопрос

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

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

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

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

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

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

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

Это было полезно?

Решение

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

java -Xms1024m -Xmx4096m com.example.Hello

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

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

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

Начиная сверху, имеем

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

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

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

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

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

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

Когда важен размер резидентного набора?

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

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

Нижняя граница

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

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

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

Другие советы

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

Лекарство состоит в том, чтобы установить этот env.переменная:

export MALLOC_ARENA_MAX=4

Если вы используете Tomcat, вы можете добавить это в TOMCAT_HOME/bin/setenv.sh файл.

Для Docker добавьте это в Dockerfile

ENV MALLOC_ARENA_MAX=4

Существует статья 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

В этом сообщении блога говорится

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

Существует также ошибка открытого JDK. JDK-8193521 «glibc тратит память с конфигурацией по умолчанию»

найдите 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

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

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

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

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

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

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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ:Для установки малых значений для -XX:MaxHeapFreeRatio необходимо также установить -XX:MinHeapFreeRatio Например

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

РЕДАКТИРОВАТЬ2:Добавлен пример реального приложения, которое запускается и выполняет одну и ту же задачу: одно с параметрами по умолчанию, другое с параметрами 10 и 25.Я не заметил никакой реальной разницы в скорости, хотя теоретически Java должна использовать больше времени для увеличения кучи в последнем примере.

Default parameters

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

MinHeap 10, MaxHeap 25

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

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

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

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

           -Xms6291456
           -Xms6144k
           -Xms6m

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

           -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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top