Как установить максимальную память без кучи в приложении Java 8 (Spring Boot)?

#java #spring-boot #memory

#java #весенняя загрузка #память

Вопрос:

У меня есть 20 встроенных приложений Tomcat Spring Boot (2.3), запущенных на компьютере Linux с 8 ГБ. Все приложения являются приложениями Java 1.8. На компьютере не хватало памяти, и в результате Linux начал убивать некоторые из моих процессов приложений.

Используя Linux top и Spring Boot admin, я заметил, что максимальная куча памяти установлена на 2 ГБ:

 java -XX: PrintFlagsFinal -version | grep HeapSize
  

В результате каждое из 20 приложений пытается получить 2 ГБ кучи (1/4 физической памяти). Используя Spring Boot admin, я мог видеть, что используется только ~ 128 МБ. Итак, я уменьшил максимальный размер кучи до 512 с помощью java -Xmx512m ... Now, Spring Boot admin показывает:

введите описание изображения здесь

1,33 ГБ выделено для пространства без кучи, но используется только 121 МБ. Почему так много выделяется для пространства без кучи? Как я могу уменьшить?

Обновить

Согласно top, каждый процесс Java занимает около 2,4 ГБ (VIRT):

 KiB Mem :  8177060 total,   347920 free,  7127736 used,   701404 buff/cache
KiB Swap:  1128444 total,  1119032 free,     9412 used.   848848 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME  COMMAND
  2547 admin    20   0  2.418g 0.372g 0.012g S  0.0  4.8  27:14.43 java
  .
  .
  .
  

Обновление 2

Я запустил jcmd 7505 VM.native_memory один из процессов, и он сообщил:

 7505:

Native Memory Tracking:

Total: reserved=1438547KB, committed=296227KB
-                 Java Heap (reserved=524288KB, committed=123808KB)
                            (mmap: reserved=524288KB, committed=123808KB)

-                     Class (reserved=596663KB, committed=83423KB)
                            (classes #15363)
                            (malloc=2743KB #21177)
                            (mmap: reserved=593920KB, committed=80680KB)

-                    Thread (reserved=33210KB, committed=33210KB)
                            (thread #32)
                            (stack: reserved=31868KB, committed=31868KB)
                            (malloc=102KB #157)
                            (arena=1240KB #62)

-                      Code (reserved=254424KB, committed=27120KB)
                            (malloc=4824KB #8265)
                            (mmap: reserved=249600KB, committed=22296KB)

-                        GC (reserved=1742KB, committed=446KB)
                            (malloc=30KB #305)
                            (mmap: reserved=1712KB, committed=416KB)

-                  Compiler (reserved=1315KB, committed=1315KB)
                            (malloc=60KB #277)
                            (arena=1255KB #9)

-                  Internal (reserved=2695KB, committed=2695KB)
                            (malloc=2663KB #19903)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=20245KB, committed=20245KB)
                            (malloc=16817KB #167011)
                            (arena=3428KB #1)

-    Native Memory Tracking (reserved=3407KB, committed=3407KB)
                            (malloc=9KB #110)
                            (tracking overhead=3398KB)

-               Arena Chunk (reserved=558KB, committed=558KB)
                            (malloc=558KB)
  

Ответ №1:

Прежде всего — нет, 1,33 ГБ не выделено. На скриншоте у вас выделено 127 МБ памяти без кучи. 1,33 ГБ — это максимальный предел.

Я вижу, что ваше метапространство составляет около 80 МБ, что не должно представлять проблемы. Остальная часть памяти может состоять из множества вещей. Сжатые классы, кэш кода, собственные буферы и т. Д…

Чтобы получить подробное представление о том, что потребляет оперативную память, вы можете запросить MBean java.lang:type=MemoryPool,name=* , например, через VisualVM с помощью плагина MBean.

Однако ваши приложения могут просто потреблять слишком много встроенной памяти. Например, виновником может быть много буферов ввода-вывода из Netty (израсходовано java.nio.DirectByteBuffer ). Если это виновник, вы можете, например, ограничить кэширование DirectByteBuffer файлов с помощью флага -Djdk.nio.maxCachedBufferSize или установить ограничение с -XX:MaxDirectMemorySize помощью . Для получения окончательного ответа о том, что именно потребляет вашу оперативную память, вам нужно будет создать дамп кучи и проанализировать его.

Итак, чтобы ответить на ваш вопрос «Почему так много выделяется для пространства без кучи? Как я могу уменьшить? » Для пространства без кучи выделено не так много. Большая часть этого — собственные буферы для ввода-вывода и внутренние компоненты JVM. Не существует универсального переключателя или флага для одновременного ограничения всех различных кэшей и пулов.

Теперь обратимся к слону в комнате. Я думаю, что ваша реальная проблема связана с тем, что у вас просто очень мало оперативной памяти. Вы сказали, что используете 20 экземпляров JVM, объем которых ограничен 512 МБ кучи на компьютере объемом 8 ГБ. Это неустойчиво. 20 x 512 МБ = 10 ГБ кучи, что больше, чем вы можете вместить с 8 ГБ общей оперативной памяти. И это еще до того, как вы начнете считать во внешней / встроенной памяти. Вам нужно либо предоставить больше ресурсов HW, уменьшить количество JVM, либо еще больше уменьшить кучу / метапространство и другие ограничения (чего я настоятельно не советую делать).

Комментарии:

1. Спасибо. Это очень полезно. Однако у меня все еще есть одно сомнение. Согласно top, объем каждого из моих Java-процессов составляет ~ 2,4 ГБ. Если выделено только 512 МБ места в куче 127 МБ без кучи, то почему VIRT сообщает о 2,5 ГБ?

2. Вершина вершины — это виртуальная память man7.org/linux/man-pages/man1/top.1.html Он включает в себя весь код, данные и общие библиотеки, а также страницы, которые были заменены, и страницы, которые были сопоставлены, но не использовались. Итак, многое суммируется, я бы поспорил, что большая часть этого распределяется между процессами. Статистика RES (резидентная память) гораздо более информативна, хотя все еще содержит немного неиспользуемых страниц памяти, которые не были восстановлены. В вашем случае он показывает 0,372г, что не является огромным количеством.

3. Я думал, что top показывает только память, выделенную исключительно для одного процесса. Я побежал jmcd (обновленная информация в моем OP). Он показывает выделенные ~ 300 МБ, но зарезервированные ~ 1,4 ГБ.

4. Что ж, отлично, это соответствует тому, что мы видели до сих пор. зарезервировано 1,4 ГБ, что в основном является пределом всей памяти с выделенными / выделенными 300 МБ этого пространства. Куча выглядит так, как и ожидалось — максимальный лимит 512 МБ и используется 120 МБ. 80 МБ метапространства (показатель «Класс»). 32 МБ занимают стеки потоков, а остальная часть еще меньше. 300 МБ x 20 ~ = 6 ГБ, поэтому вы работаете опасно близко к пределу оперативной памяти (оставляя часть для ОС и других процессов). Теперь, если внезапная загрузка появится на одном или нескольких JVM, она может легко достичь предела.

5. Спасибо Лепрекон. Я добавил 32 ГБ физической памяти в поле. Теперь top отображается 3,3 ГБ свободного места. Я ожидал намного больше свободной памяти, чем это. Я сохранил пространство кучи каждой JVM до 512 МБ, поэтому не уверен, почему свободно только 3,3 ГБ.

Ответ №2:

В дополнение к тому, что уже было указано, вот очень хорошая статья о метапространстве в JVM, которое по умолчанию резервирует около 1 ГБ (хотя на самом деле оно может использовать не так много). Итак, это еще одна вещь, которую вы можете настроить с помощью флага -XX:MaxMetaspaceSize , если у вас много небольших приложений и вы хотите уменьшить объем используемой / зарезервированной памяти.