мы пытаемся исследовать использование памяти процессом Java при умеренной нагрузке.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12663 test 20 0 8378m 6.0g 4492 S 43 8.4 162:29.95 java
Как видите, у нас есть резидентная память на 6 ГБ. Теперь самое интересное: процесс выполняется с этими параметрами:
Глядя на эти настройки и на фактическое использование памяти, мы споткнулись, чтобы увидеть разницу в том, что мы ожидаем от этого процесса, и что он на самом деле использует.
Обычно наши проблемы с памятью решаются путем анализа дампа кучи, но в этом случае наша память используется где-то вне кучи.
Вопросы: каковы были бы шаги, чтобы попытаться найти причину такого высокого использования памяти? Какие инструменты могут помочь нам определить, что использует память в этом процессе?
ИЗМЕНИТЬ 0
Не похоже, что это проблема, связанная с кучей, поскольку у нас все еще есть достаточно места:
jmap -heap 12663
приводит к (отредактировано для экономии места)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 536870912 (512.0MB)
MaxNewSize = 536870912 (512.0MB)
OldSize = 1610612736 (1536.0MB)
NewRatio = 7
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
New Generation: 45.7% used
Eden Space: 46.3% used
From Space: 41.4% used
To Space: 0.0% used
concurrent mark-sweep generation: 63.7% used
Perm Generation: 82.5% used
ИЗМЕНИТЬ 1
используя pmap, мы видим, что есть довольно много выделений в 64 МБ:
pmap -x 12663 | grep rwx | sort -n -k3 | less
приводит к:
... a lot more of these 64Mb chunks
00007f32b8000000 0 65508 65508 rwx-- [ anon ] <- what are these?
00007f32ac000000 0 65512 65512 rwx-- [ anon ]
00007f3268000000 0 65516 65516 rwx-- [ anon ]
00007f3324000000 0 65516 65516 rwx-- [ anon ]
00007f32c0000000 0 65520 65520 rwx-- [ anon ]
00007f3314000000 0 65528 65528 rwx-- [ anon ]
00000000401cf000 0 241904 240980 rwx-- [ anon ] <- Direct memory ?
000000077ae00000 0 2139688 2139048 rwx-- [ anon ] <- Heap ?
Итак, как узнать, что это за фрагменты по 64 МБ? Что их использует? Какие в них данные?
Спасибо
Проблема может быть связана с этим проблема с glibc.
По сути, когда у вас есть несколько потоков, выделяющих память, glibc будет увеличивать количество доступных арен для выделения, чтобы избежать конфликта блокировок. Размер арены - 64 Мб. Верхний предел - создание арен в 8 раз большее количество ядер. Арены будут создаваться по запросу, когда поток обращается к арене, которая уже заблокирована, поэтому она со временем увеличивается.
В Java, где вы разбрасываете потоки, это может быстро привести к созданию множества арен. И распределения распределяются по всем этим аренам. Первоначально каждая арена размером 64 Мбайт - это просто отображенная незавершенная память, но по мере выделения вы начинаете использовать для них реальную память.
Скорее всего, ваш pmap содержит списки, подобные приведенным ниже. Обратите внимание, как 324K + 65212K = 65536K, 560K + 64976K == 65536K, 620K + 64916K == 65536K. То есть в сумме они составляют 64Мб.
00007f4394000000 324K rw--- [ anon ] 00007f4394051000 65212K ----- [ anon ] 00007f4398000000 560K rw--- [ anon ] 00007f439808c000 64976K ----- [ anon ] 00007f439c000000 620K rw--- [ anon ] 00007f439c09b000 64916K ----- [ anon ]
Что касается обходные пути: В ошибке упоминаются некоторые параметры среды, которые вы можете установить для ограничения количества арен, но вам нужна достаточно высокая версия glibc.
Как насчет Ламдба Зонд? Среди прочего, он может показать вам разбивку использования памяти, как на скриншоте ниже:
Иногда pmap -x your_java_pid
также может быть полезным.
JProfiler может быть тем, что вы ищете, но это не бесплатно. Еще один хороший и бесплатный инструмент для исследования использования памяти Java-процессом - это Java VisualVM, доступный как инструмент JDK в дистрибутивах Oracle / Sun JDK. Я лично рекомендую более целостный подход к проблеме (т.е. для мониторинга JDK + OS + дисков и т. Д.) - использование какой-либо системы мониторинга сети - Nagios, Verax NMS или OpenNMS.
Проблема находится вне кучи, поэтому лучшими кандидатами являются:
JNI leak
Allocation of direct memory buffer
Из-за того, что вы ограничили размер прямого буфера, лучшим кандидатом, на мой взгляд, является утечка JNI.
Существует удобный инструмент для просмотра распределения памяти кучи, включенной в JDK, под названием jmap, помимо этого у вас также есть стек и т. Д. (Xss). Выполните эти две команды jmap, чтобы получить дополнительную информацию об использовании памяти:
jmap -heap <PID>
jmap -permstat <PID>
Чтобы получить еще больше информации, вы можете подключиться к процессу с помощью jconsole (также включенного в JDK). Однако Jconsole требует, чтобы JMX был настроен в приложении.
Используйте JVisualVM. Он имеет различные представления, которые сообщают вам, сколько памяти кучи используется, PermGen и так далее и так далее.
Что касается ответа на ваш вопрос. Java обрабатывает память совсем не так, как вы могли ожидать.
Когда вы устанавливаете параметры -Xms и -Xmx, вы сообщаете JVM, сколько памяти она должна выделить в куче для начала, а также сколько она должна выделить как максимум.
Если у вас есть приложение Java, которое использовало в общей сложности 1 м памяти, но передало в -Xms256m -Xmx2g, тогда JVM инициализируется с использованием 256 м памяти. Он не будет использовать меньше этого. Не имеет значения, что ваше приложение использует только 1 м памяти.
Во-вторых. В приведенном выше случае, если ваше приложение в какой-то момент использует более 256 МБ памяти, JVM выделит столько памяти, сколько необходимо для обслуживания запроса. Однако это не буду уменьшите размер кучи до минимального значения. По крайней мере, не в большинстве случаев.
В вашем случае, поскольку вы устанавливаете минимальную и максимальную память на 2g, JVM будет выделять 2g в начале и поддерживать его.
Управление памятью Java довольно сложно, и настройка использования памяти может быть задачей сама по себе. Однако есть много ресурсов, которые могут помочь.