Потоковые запущенные процессы не будут уничтожать (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();
}
Ответы
Ответ 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();