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

Потоковые запущенные процессы не будут уничтожать (Java)

Запуск нескольких потоков и с каждым exec(), затем destroy() выполняемый java-процесс приводит к тому, что часть процесса не будет уничтожена и все еще запущена после выхода программы. Вот какой код, который воспроизводит проблему. Я заметил, что чем больше потоков вы начинаете, тем больше процессов остается в живых. И чем больше сна перед разрушением(), тем меньше процессов остается в живых. (Я использовал InfiniteLoop в качестве примера. Любой запущенный процесс будет делать трюк.)

EDIT: Сообщается об ошибке в Oracle, ожидая ответа. Не стесняйтесь делиться любыми знаниями/экспериментами по этому вопросу.

for(int i = 0; i < 100; i++)
{
  new Thread(new Runnable()
  {
    public void run()
    {
      try
      {
        Process p = Runtime.getRuntime().exec(new String[]{"java", "InfiniteLoop"});
        Thread.sleep(1);
        p.destroy();
      }catch(IOException | InterruptedException e){e.printStackTrace();}                    
    }
  }).start();
}
4b9b3361

Ответ 1

Если подпроцессы пишут что-либо в stdout или stderr (намеренно или нет), это может вызвать проблемы:

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

Источник: http://www.javaworld.com/jw-12-2000/jw-1229-traps.html

Всю статью стоит прочитать ИМО, если вам нужно использовать Runtime.exec().

Ответ 2

Используйте p.waitFor(); до p.destroy();,

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

Ответ 3

Это просто потому, что перед тем, как потоки выполняют вызов destroy, ваша основная программа завершается и все связанные потоки оставляют запущенные процессы запущенными. Чтобы убедиться в этом, просто добавьте вызов System.out после уничтожения, и вы обнаружите, что он не выполнен. Чтобы преодолеть это, добавьте Thread.sleep в конце вашего основного метода, и у вас не будет осиротевших процессов. Нижеследующее не оставляет никакого процесса.

public class ProcessTest {

public static final void main (String[] args) throws Exception {

    for(int i = 0; i < 100; i++) {
        new Thread(new Runnable()
            {
                public void run() {
                    try {
                        Process p = Runtime.getRuntime().exec(new String[]{"java", "InfiniteLoop"});
                        Thread.sleep(1);
                        p.destroy();
                        System.out.println("Destroyed");
                    }catch(IOException e) {
                        System.err.println("exception: " + e.getMessage());
                    } catch(InterruptedException e){
                        System.err.println("exception: " + e.getMessage());
                    }
                }
            }).start();
    }


    Thread.sleep(1000);

}

}

Ответ 4

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

Ответ 5

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

import java.io.*;

class Mp {
public static void main(String []args) {
    for(int i = 0; i < 100; i++) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("1");
                    Process p = Runtime.getRuntime().exec
                        (new String[]{"notepad", ""});
                    System.out.println("2");
                    Thread.sleep(5);
                    System.out.println("3");
                    p.destroy();
                    System.out.println("4");
                }
                catch(IOException | InterruptedException e) {
                    e.printStackTrace();
                }                    
            }
        }).start();
    }
}
}

Ответ 6

Это не ответ; Я отправляю полный исходный текст для своей собственной попытки воссоздать эту проблему в соответствии с комментариями к обсуждению.

Я не могу воспроизвести эту проблему на Ubuntu 12.04; OpenJDK 6b_27 (см. Ниже).

ProcessTest.java:

import java.io.*;

public class ProcessTest {

    public static final void main (String[] args) throws Exception {

        for(int i = 0; i < 100; i++) {
            new Thread(new Runnable() 
                {
                    public void run() {
                        try {
                            Process p = Runtime.getRuntime().exec(new String[]{"java", "InfiniteLoop"});
                            Thread.sleep(1);
                            p.destroy();
                        }catch(IOException e) {
                            System.err.println("exception: " + e.getMessage());
                        } catch(InterruptedException e){
                            System.err.println("exception: " + e.getMessage());
                        }                    
                    }
                }).start();
        }

    }

}

InfiniteLoop.java

public class InfiniteLoop {
    public static final void main (String[] args) {
        while (true) ;
    }
}

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

Update:

Мне просто пришлось оставить около 5 процессов, запущенных после его завершения. Это не всегда происходит. Weird. Я тоже хочу узнать об этом. У меня есть подозрение, что это как-то связано с разрушением процесса слишком быстро или каким-то гоночным состоянием; возможно, java что-то проталкивает или что-то делает для создания нового процесса, который destroy() не заботится о том, вызван ли он слишком быстро/в неподходящее время.

Я нашел старую ошибку (но она не отмечена), заявив, что если процесс порождает подпроцессы, они не могут быть убиты командой destroy(). bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092 Какую версию JDK вы используете.

Вот еще одна ссылка на то, что похоже на аналогичную проблему: Инструмент/метод Java для принудительного уничтожения дочернего процесса И я хочу извиниться, если только добавил путаница в вашей жизни, я на самом деле не использую этот процесс, и я не знаком с причудами. Надеюсь, кто-то еще вступит в окончательный ответ. Похоже, что он не обрабатывает подпроцессы хорошо, и я предполагаю, что java виляет что-то. Это все, что я получил.

Ответ 7

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

Я работаю на Linux-машине, поэтому я буду использовать файл UNIXProcess.class для иллюстрации.

Runtime.exec(...) создаст новый ProcessBuilder и запустит его, который на машине unix создает новый экземпляр UNIXProcess. В конструкторе UNIXProcess есть этот блок кода, который фактически выполняет этот процесс в фоновом (разветвленном) потоке:

java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
    public Object run() {
    Thread t = new Thread("process reaper") {
        public void run() {
                    try {
                        pid = forkAndExec(prog,
                      argBlock, argc,
                      envBlock, envc,
                      dir,
                      redirectErrorStream,
                      stdin_fd, stdout_fd, stderr_fd);
                    } catch (IOException e) {
                        gate.setException(e); /*remember to rethrow later*/
                        gate.exit();
                        return;
                    }
                    java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction() {
                        public Object run() {
                        stdin_stream = new BufferedOutputStream(new
                                                FileOutputStream(stdin_fd));
                        stdout_stream = new BufferedInputStream(new
                                                FileInputStream(stdout_fd));
                        stderr_stream = new FileInputStream(stderr_fd);
                        return null;
                    }
                    });
                    gate.exit(); /* exit from constructor */
        int res = waitForProcessExit(pid);
        synchronized (UNIXProcess.this) {
            hasExited = true;
            exitcode = res;
            UNIXProcess.this.notifyAll();
        }
        }
    };
            t.setDaemon(true);
            t.start();
    return null;
    }
});

Обратите внимание, что фоновый поток устанавливает поле pid, которое является идентификатором процесса UNIX. Это будет использоваться destroy(), чтобы сообщить операционной системе, которая должна убить.

Поскольку нет способа убедиться, что этот фоновый поток запущен при вызове destroy(), мы можем попытаться убить процесс до его запуска или мы можем попытаться убить процесс до того, как будет задано поле pid; pid неинициализирован и, следовательно, равен 0. Поэтому я думаю, что вызов destroy слишком рано сделает эквивалент kill -9 0

В UNIXProcess destroy() есть комментарий, который ссылается на это, но только рассматривает вызов destroy после того, как процесс уже завершен, а не до его запуска:

// There is a risk that pid will be recycled, causing us to
// kill the wrong process!  So we only terminate processes
// that appear to still be running.  Even with this check,
// there is an unavoidable race condition here, but the window
// is very small, and OSes try hard to not recycle pids too
// soon, so this is quite safe.

Поле pid даже не отмечено как изменчивое, поэтому мы можем даже не видеть последнее значение все время.

Ответ 8

У меня была очень похожая проблема, и проблема с destroy() не работала, проявлялась даже с одним потоком.

Process process = processBuilder(ForeverRunningMain.class).start()
long endTime = System.currentTimeMillis() + TIMEOUT_MS; 
while (System.currentTimeMillis() < endTime) {
    sleep(50);
}
process.destroy();

Процесс не всегда разрушался, если TIMEOUT_MS был слишком низким. Добавление дополнительного sleep() до destroy() исправлено (хотя у меня нет объяснений):

Thread.sleep(300);
process.destroy();