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

Java Runtime.getRuntime(). Exec() альтернативы

У меня есть коллекция webapps, которые работают под tomcat. Tomcat настроен на 2 ГБ памяти с использованием аргумента -Xmx.

Многие из webapps должны выполнить задачу, которая в конечном итоге использует следующий код:

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
process.waitFor();
...

Проблема, с которой мы сталкиваемся, связана с тем, как этот "дочерний процесс" создается в Linux (Redhat 4.4 и Centos 5.4).

Я понимаю, что объем памяти, равный сумме tomcat, должен быть свободен в пуле физической (не-swap) системной памяти изначально для создания этого дочернего процесса. Когда нам не хватает свободной физической памяти, мы получаем следующее:

    java.io.IOException: error=12, Cannot allocate memory
     at java.lang.UNIXProcess.<init>(UNIXProcess.java:148)
     at java.lang.ProcessImpl.start(ProcessImpl.java:65)
     at java.lang.ProcessBuilder.start(ProcessBuilder.java:452)
     ... 28 more

Мои вопросы:

1) Возможно ли удалить требование для объема памяти, равного родительскому процессу, свободного в физической памяти? Я ищу ответ, который позволяет мне указать, сколько память, которую получает дочерний процесс, или разрешить java на linux для доступа к памяти подкачки.

2) Каковы альтернативы Runtime.getRuntime(). exec(), если нет решения для # 1? Я мог думать только о двух, ни один из которых не очень желателен. JNI (очень нежелательно) или переписывать программу, которую мы вызываем в java, и сделать ее собственным процессом, с которым Webapp общается как-то. Должны быть и другие.

3) Есть ли еще одна сторона этой проблемы, которую я не вижу, чтобы ее можно было исправить? Уменьшение объема памяти, используемого tomcat, не является вариантом. Увеличение объема памяти на сервере всегда является опцией, но похоже, что это более полоса.

На серверах работает java 6.

EDIT: Я должен указать, что я не ищу специального решения для tomcat. Эту проблему можно увидеть с помощью любого из приложений java, которые мы запускаем на веб-сервере (их несколько). Я просто использовал tomcat в качестве примера, потому что он, скорее всего, будет иметь большую память, выделенную для него, и там, где мы действительно видели ошибку в первый раз. Это воспроизводимая ошибка.

EDIT: в конце концов, мы решили эту проблему, переписав то, что системный вызов выполнял в java. Я чувствую, что нам повезло, что мы можем сделать это без дополнительных системных вызовов. Не все процессы смогут это сделать, поэтому я все равно буду любить видеть фактическое решение этого.

4b9b3361

Ответ 1

Попробуйте использовать ProcessBuilder. Документы говорят, что "предпочтительный" способ начать подпроцесс в наши дни. Вы также должны рассмотреть возможность использования карты окружения (документы находятся в ссылке), чтобы указать учетные данные для нового процесса. Я подозреваю (но не знаю точно), что причина, по которой ему нужно столько памяти, состоит в том, что она наследует настройки из процесса tomcat. Использование карты окружения должно позволить вам переопределить это поведение. Однако обратите внимание, что запуск процесса очень специфичен для ОС, поэтому YMMV.

Ответ 2

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

//you would probably want to make this a singleton
public class ProcessHelper
{
    private OutputStreamWriter output;
    public ProcessHelper()
    {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("java ProcessHelper");
        output = new OutputStreamWriter(process.getOutputStream());
    }
    public void exec(String command)
    {
        output.write(command, 0, command.length());
    }
}

тогда вы создадите вспомогательную java-программу

public class ProcessHelper
{
    public static void main(String[] args)
    {
         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
         String command;
         while((command = in.readLine()) != null)
         {
             Runtime runtime = Runtime.getRuntime();
             Process process = runtime.exec(command);
         }
    }
}

то, что мы по существу сделали, это сделать небольшой сервер "exec" для вашего приложения. Если вы сначала инициализируете свой класс ProcessHelper в своем приложении, он успешно создаст этот процесс, тогда вы просто передадите ему команды, потому что второй процесс намного меньше, он всегда должен быть успешным.

Вы также можете сделать свой протокол более глубоким, например, вернуть коды выхода, уведомить об ошибках и т.д.

Ответ 3

Я думаю, что это проблема unix fork(), потребность в памяти возникает из-за того, как работает fork() - он сначала клонирует образ дочернего процесса (при любом размере, который он в настоящее время), затем заменяет родительское изображение дочерним элементом образ. Я знаю, что в Solaris есть способ контролировать это поведение, но я не знаю, что это такое.

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

Ответ 5

Другой альтернативой является запуск отдельного процесса exec, который отслеживает либо файл, либо какой-либо другой тип "очереди". Вы добавляете желаемую команду к этому файлу, и сервер exec замечает его, запускает команду и как-то записывает результаты в другое место, которое вы можете получить.

Ответ 6

Лучшим решением, которое я нашел до сих пор, было использование защищенной оболочки с открытыми ключами. Используя http://www.jcraft.com/jsch/, я создал соединение с localhost и выполнил такие команды. Возможно, у этого есть немного больше накладных расходов, но для моей ситуации это работало как шарм.