Використання віртуальної пам'яті від Java під Linux, використовується занадто багато пам'яті


259

У мене проблема з додатком Java, що працює під Linux.

Коли я запускаю програму, використовуючи максимальний розмір купи за замовчуванням (64 МБ), я бачу, що за допомогою програми «вершини» виділяється 240 МБ віртуальної пам’яті. Це створює деякі проблеми з деяким іншим програмним забезпеченням на комп’ютері, яке відносно обмежене ресурсами.

Наскільки я розумію, зарезервована віртуальна пам’ять не буде використана, оскільки, як тільки ми досягнемо межі купи OutOfMemoryError, кидається Я запустив ту саму програму під windows і бачу, що розмір віртуальної пам’яті та розмір Heap схожі.

Чи все-таки я можу налаштувати Віртуальну пам'ять, яка використовується для процесу Java під Linux?

Редагувати 1 : Проблема не в Купі. Проблема полягає в тому, що, якщо, наприклад, я встановити купу у 128 Мб, все одно Linux виділяє 210 Мб віртуальної пам'яті, яка ніколи не потрібна. **

Редагування 2 : Використання ulimit -vдозволяє обмежити об'єм віртуальної пам'яті. Якщо набір розмірів нижче 204 Мб, програма не запуститься, навіть якщо йому не потрібно 204 Мбайт, лише 64 Мб. Тому я хочу зрозуміти, чому Java вимагає стільки віртуальної пам’яті. Чи можна це змінити?

Редагування 3 : У системі працює кілька інших програм, які вбудовані. А система має обмеження віртуальної пам'яті (з коментарів, важливих деталей).


Чому ви переймаєтесь використанням віртуальної пам'яті? Якщо ви дійсно хочете вас турбувати, подивіться на використання пам'яті резидента та прочитайте наступні команди: безкоштовно, ps, top.
basszero

2
У системі працює кілька інших додатків, які вбудовані. А система має обмеження у віртуальній пам'яті.
Mario Ortegón

а-а-а, чорт у деталях
basszero

Яку реалізацію Java ви використовуєте. IIRC, стандарт JRE (не-OpenJDK), без JRE, не ліцензується для вбудованого використання.
Том Хоутін - тайклін

Я думаю, що я пропустив використану "вбудовану" частину ... вона обмежена пам'яттю та обладнанням налаштовано, але це все ще стандартний комп'ютер
Mario Ortegón

Відповіді:


630

Це давня скарга на 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 кгрегорі 20 0 4373м 15м 7152 S 0 0,2 0: 00,10 ява
  • VIRT - це віртуальний простір пам'яті: сума всього, що знаходиться на карті віртуальної пам'яті (див. Нижче). Це в значній мірі безглуздо, за винятком випадків, коли цього немає (див. Нижче).
  • RES - це розмір набору резидентів: кількість сторінок, які наразі знаходяться в оперативній пам'яті. Майже у всіх випадках це єдине число, яке слід використовувати, кажучи "занадто великий". Але це все-таки не дуже вдале число, особливо якщо говорити про Java.
  • SHR - це об'єм пам'яті резидента, який ділиться з іншими процесами. Для Java-процесу це, як правило, обмежене спільними бібліотеками та JAR-файлами, відображеними на пам'ять. У цьому прикладі у мене працював лише один Java-процес, тому я підозрюю, що 7k - результат бібліотек, використовуваних ОС.
  • SWAP не увімкнено за замовчуванням і тут не відображається. Він вказує кількість віртуальної пам’яті, яка наразі знаходиться на диску, незалежно від того, перебуває вона фактично у просторі підкачки . ОС дуже добре зберігає активні сторінки в оперативній пам’яті, і єдиними засобами для заміни є (1) придбати більше пам’яті або (2) зменшити кількість процесів, тому найкраще ігнорувати це число.

Ситуація для Windows Task Manager трохи складніша. У Windows XP є стовпці «Використання пам’яті» та «Розмір віртуальної пам’яті», однак офіційна документація замовчує, що вони означають. Windows Vista та Windows 7 додають більше стовпців, і вони фактично задокументовані . З них найбільш корисним є вимірювання «Робочого набору»; це приблизно відповідає сумі ВДЕ та ЗГР в Linux.

Розуміння карти віртуальної пам'яті

Віртуальна пам'ять, яка споживається процесом, - це загальна кількість всього, що є в карті пам'яті процесу. Сюди входять дані (наприклад, купа Java), а також усі спільні бібліотеки та файли, відображені в пам'яті, які використовує програма. В Linux ви можете використовувати команду pmap, щоб побачити всі речі, відображені в просторі процесу (з цього моменту я збираюся посилатися лише на Linux, тому що це я використовую; я впевнений, що є рівноцінні інструменти для Windows). Ось уривок із карти пам’яті програми «Hello World»; вся карта пам’яті завдовжки понад 100 рядків, і це не незвично, щоб мати список тисяч рядків.

0000000040000000 36K rx-- /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 rx-- /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 rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /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Величина використовується , щоб сказати , скільки з купи «використовується» при запуску програми, а також збір сміття спускового як межа наближення.
  • JARfile, зібраний на пам'ять, у цьому випадку файл, що містить "класи JDK". Коли ви пам'ятаєте JAR-карту, ви можете дуже ефективно отримувати доступ до файлів, що знаходяться в ній (проти читання з початку). Sun JVM запам'ятовує карту пам'яті всіх JAR на класному шляху; якщо вашому коду програми потрібно отримати доступ до JAR, ви також можете зробити карту пам'яті.
  • Дані на нитку для двох потоків. Блок 1М - це стек потоків. Я не мав хорошого пояснення для блоку 4k, але @ericsoe визначив його "захисним блоком": він не має дозволу читання / запису, тому він може отримати помилку сегменту при доступі, і JVM вловлює це і перекладає це до а StackOverFlowError. У реальному додатку ви побачите десятки, якщо не сотні цих записів, повторених через карту пам'яті.
  • Одна з спільних бібліотек, що містить фактичний код JVM. Є кілька таких.
  • Спільна бібліотека для стандартної бібліотеки С. Це лише одна з багатьох речей, які завантажує JVM, які не є строго частиною Java.

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

Коли важливий розмір віртуальної пам'яті?

Карта віртуальної пам'яті містить багато матеріалів. Деякі з них доступні лише для читання, деякі - загальним, а частина виділяється, але ніколи не торкається (наприклад, майже весь 4Gb купи в цьому прикладі). Але операційна система досить розумна, щоб завантажувати лише те, що їй потрібно, тому розмір віртуальної пам'яті значною мірою не має значення.

Там, де важливий розмір віртуальної пам'яті, це якщо ви працюєте в 32-бітній операційній системі, де ви можете виділити лише 2 Гб (або, в деяких випадках, 3 Гб) адресного простору процесу. У такому випадку ви маєте справу з дефіцитним ресурсом, і, можливо, доведеться робити компроміси, такі як зменшення розміру вашої купи, щоб зробити карту пам'яті великим файлом або створити багато потоків.

Але, враховуючи, що 64-розрядні машини є всюдисущими, я не думаю, що це буде задовго до того, як розмір віртуальної пам'яті стане абсолютно нерелевантною статистикою.

Коли важливий розмір резидента?

Resident Set розмір - це частина простору віртуальної пам'яті, яка фактично знаходиться в оперативній пам'яті. Якщо ваш RSS стане значною частиною вашої загальної фізичної пам’яті, можливо, прийшов час почати хвилюватися. Якщо ваш RSS зростає, займаючи всю вашу фізичну пам’ять, і ваша система починає мінятися, вже минув час починати хвилюватися.

Але RSS також вводить в оману, особливо на слабо завантаженій машині. Операційна система не витрачає багато зусиль на повернення сторінок, які використовуються в процесі. Користуючись цим, мало користі, і потенціал для дорогої помилки сторінки, якщо процес торкнеться сторінки в майбутньому. Як результат, статистика RSS може містити багато сторінок, які не використовуються.

Нижня лінія

Якщо ви не поміняєте місцями, не переймайтеся надто великою статистикою пам'яті. З застереженням, що постійно зростаючий RSS може вказувати на якусь витік пам'яті.

З програмою Java набагато важливіше звернути увагу на те, що відбувається в купі. Загальна кількість споживаного місця важлива, і є кілька кроків, які можна вжити, щоб зменшити це. Більш важливим є кількість часу, який ви проводите на збирання сміття, і які частини купи збираєте.

Доступ до диска (тобто до бази даних) коштує дорого, а пам'ять - недорого. Якщо ви можете торгувати один за іншим, зробіть це.


9
Вам слід врахувати, що частини вимірювальної пам’яті, які зараз замінені, відсутні у заході ВДЕ. Тож у вас може бути низьке значення ВДЕ, але тільки тому, що програма була неактивною, і значна частина купи була замінена на диск. Java робить дуже погану роботу Wrt для заміни: На кожному повному GC більша частина купи прогулюється та копіюється, тому якщо значна частина вашої купи була свопом, GC повинен завантажити все це в основну пам'ять.
jrudolph

1
Чудова відповідь kdgregory! Я працюю у вбудованому середовищі, використовуючи CF, у якому немає місця для заміни. Отже, виходячи з вашої відповіді, усі мої значення VIRT, SWAP та nFLT походять з файлів, відображених у пам'яті ... що тепер має сенс для мене. Чи знаєте ви, чи значення SWAP представляє сторінки, які ще не завантажені в пам'ять, або сторінки, які були замінені з пам'яті, або обидві? Як ми можемо скласти уявлення про можливе обмолот (безперервна карта, а потім заміни)?
Jeach

2
@Jeach - Я був здивований тим, що повідомлялося про будь-який своп, тому завантажив мій "подорожуючий Linux" (палець з Ubuntu 10.04 і жодний своп). Коли я ввімкнув стовпчик "SWAP" вгорі , я побачив, що Eclipse мав 509м. Коли я тоді переглянув це pmap , загальний віртуальний простір становив 650м. Тож я підозрюю, що цифра "SWAP" відображає всі сторінки на диску, а не лише ті, що не в пам'яті.
kdgregory

2
Що стосується вашого другого запитання: якщо ви постійно читаєте сторінки з флеш-карти, ваш час очікування вводу-виведення (показаний у верхньому резюме як "% wa") повинен бути високим. Однак остерігайтеся, що це буде високим для будь-якої діяльності, особливо пишете (якщо припустити, що ваша програма займається будь-яким).
kdgregory

1
> Блок 1М - стек потоків; Я не знаю, що входить у блок 4K. Блок 4K - який позначений як такий, що не має ні прав на читання, ні на запис - ймовірно, захисний блок. При переповненні стека доступ до цієї області, що викликає помилку, з якою JVM може потім впоратися, створивши Java StackOverflowException. Це набагато дешевше, ніж перевірка покажчика стека при кожному виклику методу. Області охорони без встановлених дозволів також можна побачити в інших контекстах.
eriksoe

38

Існує відома проблема з Java та glibc> = 2,10 (включає Ubuntu> = 10,04, RHEL> = 6).

Лікування полягає у встановленні цього оточення. змінна:

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 для отримання додаткових посилань.

Ви можете налаштувати також інші параметри для оптимізації для низької фрагментації виділеної пам'яті:

# 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

Ця відповідь справді допомогла мені на 64-бітному сервері Ubuntu з сервером TomEE, який отримав літеру для "споживання пам'яті". Посилання на статтю IBM справді є глибоким поясненням. Ще раз дякую за цей гарний натяк!
MWiesner

1
JVM може просочити вбудовану пам'ять, що призводить до подібних симптомів. Див. Stackoverflow.com/a/35610063/166062 . Незакриті екземпляри GZIPInputStream та GZIPOutputStream також можуть бути джерелом витоку.
Ларі Хотарі

3
У Java 8 є помилка JVM, яка призводить до необмеженого природної пам’яті: bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293 - Якщо це впливає на вас, використання MALLOC_ARENA_MAXможе уповільнити зростання вашої пам’яті, але не вирішити проблему цілком.
outofcoffee

@LariHotari дуже цінують ваші зусилля щодо вказівки на версію glibc та redhat
Сем

2
Java 8u131 містить підтримувані виправлення для відповідної помилки JVM JDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124 .
Ларі Хотарі

9

Обсяг пам’яті, виділений на процес Java, майже не відповідає тому, що я очікував. У мене були подібні проблеми із запуском Java у вбудованих системах з обмеженою пам'яттю. Запуск будь-якої програми з довільними обмеженнями VM або в системах, які не мають належної кількості свопів, мають тенденцію до порушення. Схоже, це характер багатьох сучасних додатків, які не призначені для використання в системах з обмеженими ресурсами.

У вас є ще кілька варіантів, які ви можете спробувати обмежити слід пам'яті JVM. Це може зменшити слід віртуальної пам'яті:

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

-XX: MaxPermSize = 64м Розмір постійного покоління. [5.0 і новіші: 64-розрядних віртуальних машин масштабується на 30% більше; 1,4 amd64: 96m; 1.3.1 -клієнт: 32м.]

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

Ви також можете спробувати це зробити, щоб ВМ менш агресивно ставився до вирощування купи:

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

Крім того, наскільки я пам’ятаю, експериментуючи з цим кілька років тому, кількість завантажених рідних бібліотек мала величезний вплив на мінімальний слід. Завантаження java.net.Socket додано більше 15 мільйонів, якщо я пам'ятаю правильно (а я, мабуть, ні).


7

Sun JVM потребує багато пам'яті для HotSpot, і він відображає в бібліотеках часу виконання загальну пам'ять.

Якщо пам'ять є проблемою, розгляньте можливість використання іншого JVM, придатного для вбудовування. IBM має j9, і є "jamvm" з відкритим кодом, який використовує бібліотеки класів GNU. Також у Sun є JVM Squeak, що працює на SunSPOTS, тому є альтернативи.


Це варіант відключення гарячої точки?
Mario Ortegón

Можливо, Перевірте параметри командного рядка для використовуваного JVM.
Thorbjørn Ravn Andersen

3

Просто подумав, але ви можете перевірити вплив на ulimit -vопції .

Це не є фактичним рішенням, оскільки воно обмежує адресний простір, доступний для всіх процесів, але це дозволить вам перевірити поведінку вашої програми з обмеженою віртуальною пам'яттю.


Саме це і є моя проблема. У моїй купі встановлено 64M, але Linux резервує 204MB. Якщо я встановив ulimit нижче 204, програма взагалі не працює.
Mario Ortegón

Цікаво: встановлення ulimit може мати небажаний побічний ефект для інших процесів, пояснюючи, чому програма не може запуститися.
VonC

Проблема, здається, полягає в тому, що Java вимагає зарезервувати цей більший об'єм віртуальної пам'яті, навіть не використовуючи її. У Windows віртуальна пам’ять, що використовується, і налаштування Xmx досить близькі.
Mario Ortegón

Ви спробували це з JVockit JVM?
VonC

Оскільки розподіл пам'яті JVM є сумою розподілу Heap та розміру Perm (перший можна виправити за допомогою параметрів -Xms та -Xmx), ви спробували деякі параметри з -XX: PermSize та -XX: MaxPermSize (за замовчуванням від 32 Мб до 64 Мб залежно від версії JVM)?
VonC

3

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

EDIT: Щоб встановити малі значення для -XX: MaxHeapFreeRatio, ви також повинні встановити -XX: MinHeapFreeRatio Напр.

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

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

Параметри за замовчуванням

Зрештою, максимальна купа - 905, використана купа - 378

MinHeap 10, MaxHeap 25

Зрештою, максимальна купа - 722, використана купа - 378

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


1

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


1
Проблема у мене не в розмірі Heap Size, а в обсязі віртуальної пам'яті, який призначається Linux
Mario Ortegón

Прочитайте пояснення kdgregory. Зменшення розміру купи, "Нового розміру" та інших настроюваних параметрів зменшить об'єм реальної пам'яті, яку займає jvm.
Пол Томблін

У нього можуть виникнути законні проблеми. Деякі програми (на зразок одного, який я писав) створюють файл 1 Гб, а деякі системи мають лише 2 ГБ віртуальної пам’яті, деякі з яких заповнюються спільними бібліотеками. І якщо це проблема, він обов'язково повинен відключити рандомізацію DSO. Є варіант в / proc.
Зан Лінкс

0

Ні, ви не можете налаштувати об'єм пам'яті, необхідний для VM. Однак зауважте, що це віртуальна пам'ять, а не резидент, тому вона просто залишається там без шкоди, якщо фактично не використовується.

Побічно, ви можете спробувати інший JVM, а потім Sun, із меншим розміром пам’яті, але я не можу порадити тут.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.