Из какой версии ядра Linux / libc Java Runtime.exec () безопасна в отношении памяти?

При работе одной из наших целевых платформ является мини-сервер с ограниченными ресурсами под управлением Linux (ядро 2.6.13, специальный дистрибутив на основе старого ядра Fedora). Приложение написано на Java (Sun JDK 1.6_04). Убийца Linux OOM настроен на завершение процессов, когда использование памяти превышает 160 МБ. Даже при высокой нагрузке наше приложение никогда не превышает 120 МБ, и вместе с некоторыми другими активными собственными процессами мы остаемся в пределах ограничений OOM.

Однако оказывается, что метод Java Runtime.getRuntime (). Exec (), канонический способ выполнения внешних процессов из Java, имеет особенно неудачная реализация в Linux, который заставляет порожденные дочерние процессы (временно) требовать тот же объем памяти, что и родительский процесс, поскольку адресное пространство копируется. В итоге наше приложение убивает убийца OOM, как только мы выполняем Runtime.getRuntime (). Exec ().

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

После публикация об этой проблеме в Интернете я получил некоторую обратную связь, указывающую на то, что этого не должно происходить в «более новых» версиях Linux, поскольку они реализуют метод posix fork () с использованием копирования при записи, что, предположительно, означает, что он будет копировать только те страницы, которые ему нужно изменить, когда он требуется вместо всего адресного пространства сразу.

Мои вопросы:

  • Это правда?
  • Это что-то в ядре, в реализации libc или где-то еще?
  • Из какой версии ядра / libc / независимо от того, доступно ли копирование при записи для fork ()?

Это виртуальный, а не память физический, которая необходима между вызовом fork () и последующим exec (). Я очень сомневаюсь, что у вас заканчивается виртуальная память, учитывая размер адресного пространства по сравнению с вашим пределом физической памяти.

Charles Duffy 17.10.2008 05:05

Безусловно, у нас не заканчивается физическая память, но какая-то часть Linux, кажется, думает, что это так.

Boris Terzic 17.10.2008 09:36
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
23
2
6 776
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

1: Да. 2: Это делится на два этапа: Любой системный вызов, такой как fork (), привязывается glibc к ядру. Ядро часть системного вызова находится в kernel / fork.c 3: я не знаю. Но держу пари, что в вашем ядре он есть.

Убийца OOM срабатывает, когда возникает угроза нехватки памяти на 32-битных коробках. У меня никогда не было проблем с этим, но есть способы держать OOM в страхе. Эта проблема может быть связана с проблемой конфигурации OOM.

Поскольку вы используете приложение Java, вам следует подумать о переходе на 64-битный Linux. Это обязательно должно исправить. Большинство 32-битных приложений могут без проблем работать на 64-битном ядре, если установлены соответствующие библиотеки.

Вы также можете попробовать ядро ​​PAE для 32-битной Fedora.

Почему переход на 64-битную JVM исправит это?

Alastair McCormack 20.04.2014 12:26

@AlastairMcCormack 32-битные системы Linux использовали разделение 3 ГБ / 1 ГБ для пользовательского пространства и ядра. Часть 1 ГБ зарезервирована для процессов пользовательского пространства, которым требуется доступ к пространству ядра через системные вызовы. Это ТОЛЬКО 128М. Я считаю, что нехватка памяти как в ядре 128 МБ, так и в пользовательском пространстве 3 ГБ вызовет OOM. Хотя в пространстве 3 ГБ могут быть другие процессы, JVM с большей вероятностью станет самым большим процессом и, следовательно, целью OOM. Кроме того, процесс разветвления, такой как OPs JVM, имеет более высокий балл OOM, поскольку каждый ребенок добавляет к баллу родителей. 64 Bit не имеет разделения памяти или какого-либо разделения ядра / пользователя.

Bash 21.04.2014 22:26

Что ж, я лично сомневаюсь, что это правда, поскольку fork () Linux выполняется через копирование при записи, потому что Бог знает, когда (по крайней мере, в ядрах 2.2.x он был, а это было где-то в 199x).

Поскольку OOM killer считается довольно грубым инструментом, который, как известно, пропускает зажигание (например, он не обязательно убивает процесс, который фактически выделил большую часть памяти) и который следует использовать только в качестве последнего ответа, неясно, как мне, почему вы настроили его для стрельбы на 160M.

Если вы хотите наложить ограничение на выделение памяти, то вам в помощь ulimit, а не OOM.

Мой совет - оставить OOM в покое (или отключить его вообще), настроить ulimits и забыть об этой проблеме.

Спасибо за информацию, но, к сожалению, у нас нет контроля над платформой: аппаратное обеспечение, ОС и ее конфигурация в основном высечены из камня (мы, так сказать, «просто» развертываем на ней).

Boris Terzic 17.10.2008 09:37

Да, это абсолютно верно даже для новых версий Linux (мы используем 64-битную Red Hat 5.2). У меня была проблема с медленно работающими подпроцессами около 18 месяцев, и я никогда не мог понять проблему, пока не прочитал ваш вопрос и не проверил тест, чтобы проверить его.

У нас есть блок на 32 ГБ с 16 ядрами, и если мы запустим JVM с такими настройками, как -Xms4g и -Xmx8g, и запустим подпроцессы с помощью Runtime.exec () с 16 потоками, мы не сможем запустить наш процесс быстрее, чем примерно 20 вызовов процесса в секунду.

Попробуйте это с помощью простой команды «date» в Linux примерно 10 000 раз. Если вы добавляете код профилирования, чтобы наблюдать, что происходит, он запускается быстро, но со временем замедляется.

Прочитав ваш вопрос, я решил попробовать понизить настройки памяти до -Xms128m и -Xmx128m. Теперь наш процесс выполняется со скоростью около 80 вызовов в секунду. Я изменил только настройки памяти JVM.

Кажется, он не забирает память таким образом, чтобы у меня когда-либо не хватало памяти, даже когда я пробовал это с 32 потоками. Просто необходимо каким-то образом выделить дополнительную память, что приводит к большим затратам на запуск (и, возможно, остановку).

В любом случае, похоже, что должна быть настройка, чтобы отключить это поведение Linux или, может быть, даже в JVM.

Ответ принят как подходящий

Это в значительной степени способ, которым * nix (и Linux) работали с незапамятных времен (или на заре mmus).

Чтобы создать новый процесс в * nixes, вы вызываете fork (). fork () создает копию вызывающего процесса со всеми его отображениями памяти, файловыми дескрипторами и т. д. Отображения памяти выполняются копированием при записи, поэтому (в оптимальных случаях) фактически не копируется никакая память, а только отображения. Следующий вызов exec () заменяет текущее отображение памяти сопоставлением нового исполняемого файла. Итак, fork () / exec () - это способ создания нового процесса, и это то, что использует JVM.

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

«Обходной путь» состоит в том, чтобы сделать то, что вы уже сделали, создать внешний легкий процесс, который заботится о создании новых процессов - или использовать более легкий подход, чем fork / exec, для создания процессов (чего в Linux нет - и в любом случае требуется изменение самого jvm). Posix определяет функцию posix_spawn (), которая теоретически может быть реализована без копирования отображения памяти вызывающего процесса, но в Linux это не так.

Я бы назвал этот легкий процесс ssh и использовал jcraft.com/jsch для подключения к localhost.

JoG 23.08.2013 19:36

Другие вопросы по теме