Использование виртуальной памяти из Java под Linux, слишком много памяти - PullRequest
237 голосов
/ 18 февраля 2009

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

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

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

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

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

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

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

Ответы [ 8 ]

589 голосов
/ 18 февраля 2009

Это была давняя жалоба на 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
...

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

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

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

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

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

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

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

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

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

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

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

Итог

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

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

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

35 голосов
/ 09 марта 2015

Известна проблема с 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
9 голосов
/ 18 февраля 2009

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

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

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

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

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

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

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

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

7 голосов
/ 03 марта 2009

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

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

3 голосов
/ 16 января 2015

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

РЕДАКТИРОВАТЬ: Чтобы установить небольшие значения для -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

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

3 голосов
/ 18 февраля 2009

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

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

1 голос
/ 18 февраля 2009

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

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

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Укажите максимальный размер в байтах пула выделения памяти. Это значение должно быть кратно 1024 больше 2МБ. Добавить букву k или К для обозначения килобайт, или М или М указывать мегабайты. По умолчанию значение составляет 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

0 голосов
/ 18 февраля 2009

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

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

...