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

StackOverflow https://stackoverflow.com/questions/209875

Вопрос

На работе одной из наших целевых платформ является мини-сервер с ограниченными ресурсами под управлением 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()?
Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top