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

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

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

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

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

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

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

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

8 ответов


это была давняя жалоба с 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 это обычно ограничивается общими библиотеками и сопоставленными с памятью JARfiles. В этом примере у меня был только один процесс 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
...

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

начиная с самого верха, у нас есть

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

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

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

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

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

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

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

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

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

Итог

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

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

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


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

лекарство это ОКР. переменная: export MALLOC_ARENA_MAX=4 Если вы используете Tomcat, вы можете добавить это .

есть статья 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 на Гугле или более ссылок.

вы можете настроить и другие параметры 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 на встроенных/ограниченных системах памяти. Бег!--1 - >любой приложение с произвольными ограничениями VM или на системах, которые не имеют достаточного количества свопа, как правило, ломаются. Похоже, это характер многих современных приложений, которые не предназначены для использования в системах с ограниченными ресурсами.

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

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

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

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

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

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

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


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

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


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

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


одним из способов уменьшения кучи системы с ограниченными ресурсами может быть игра с переменной-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

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


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

-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


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

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