Подтвердить что ты не робот

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

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

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

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

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

Мои вопросы:

  • Это правда?
  • Это что-то в ядре, реализация libc или где-то еще?
  • В какой версии ядра /libc/what есть копирование на запись для fork()?
4b9b3361

Ответ 1

Это в значительной степени способ, с помощью которого nix (и linux) работали с самого начала (или atleat the dawn of mmus).

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

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

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

Ответ 2

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

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

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

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

Ответ 3

Да, это абсолютно так в случае с новыми версиями 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.

Ответ 4

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

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

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

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