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

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

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

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

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

Мои вопросы:

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

Ответы [ 4 ]

11 голосов
/ 16 декабря 2009

Это почти то же самое, что * nix (и linux) работали с незапамятных времен (или даже с рассветом mmus).

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

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

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

5 голосов
/ 17 октября 2008

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

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

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

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

2 голосов
/ 16 декабря 2009

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

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

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

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

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

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

1 голос
/ 17 октября 2008

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...