Использование виртуальной памяти из 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 теоретически должна использовать больше времени для увеличения кучи в последнем примере.
в конце, максимальная куча 905, используемая куча 378
в конце, максимальная куча 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, с меньшим объемом памяти, но я не могу посоветовать здесь.