2013-08-07 5 views
13

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

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(); 
} 
+0

Вы получаете исключение? – SLaks

+0

Не исключение. – Bastien

+0

Откуда вы знаете? – SLaks

ответ

1

Я считаю, что в соответствии с link отчетливый процесс порождается операционной системой в ответ на этот призыв. Этот процесс не зависит от вашей 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(); 
    } 
} 
} 
+0

Да, но его код убивает все процессы. – SLaks

+0

Хороший момент - я забыл про вызов уничтожения. Было бы полезно добавить e.printStackTrace() в блок исключений, чтобы увидеть, происходят ли какие-либо ошибки. –

+0

Было бы также полезно добавить некоторые трассы println в потоки, чтобы узнать, действительно ли достигнут destroy(). –

3

Если подпроцессы ничего на стандартный вывод или поток ошибок (намеренно или нет) написать, что может вызвать проблемы:

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

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

Вся статья IMO стоит читать, если вам нужно использовать Runtime.exec().

+0

Ничего не пишет, это просто пусто (true) {} – Bastien

0

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

Я не могу воспроизвести эту проблему на 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 завершается. Однако, если я добавлю длинную задержку в основной поток после запуска потоков, но перед возвратом из основного, я вижу примерно дюжину запущенных java-процессов, которые торчат (хотя они завершаются, когда основная программа завершается).

Update:

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

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

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

+0

У меня все еще возникает проблема с этим кодом. Я пробовал на другой машине с Fedora, той же проблемой. Но я попробовал на Windows 7 64 в виртуальной коробке, и проблем не было! Связано ли это с ОС? – Bastien

+0

Ubuntu не работает, ознакомьтесь с комментариями, которые я оставил в вашем вопросе. Я сталкиваюсь с той же проблемой, случайно. –

+0

Почему бы не изменить это в вопросе, а не опубликовать его как ответ? – DannyMo

3

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

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

+1

'p.waitFor()' ждет завершения процесса, поэтому в случае с 'InfiniteLoop' он никогда не вернется. Я думаю, что 'exec()' point; Я не могу найти ничего в документации, которая гарантирует, что процесс действительно был запущен, когда 'exec()' возвращает, хотя я предполагаю, что это произошло потому, что он имеет возможность, например, бросать ошибки, когда команда не найдена. –

+0

Неужели он не ждет возврата текущего процесса? а затем p.destroy убивает процесс .. он завершает один цикл создания и уничтожения на одной итерации. – Abhishek

+0

p.waitFor() ждет exec(), чтобы вернуться, поэтому используя это как маркер ..вы можете проверить работу whethr exec() или нет. – Abhishek

0

Состояние гонки между временем 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; ПИД- неинициализированный и поэтому 0. Таким образом, я думаю, призывая уничтожить слишком рано делать эквивалент kill -9 0

Существует даже комментарий в UNIXProcess 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. 

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

+3

Состояние гонки не имеет Здесь нет. Конструктор 'UNIXProcess' не возвращается до * после того, как * вызов' forkAndExec' в потоке завершен - для этого используется 'gate'. Фактически, именно так он может генерировать исключение, если процесс не запускается, вы заметите, что исключение происходит из этого фонового потока. Поэтому конструктор не будет возвращаться до тех пор, пока процесс не будет запущен, и поэтому невозможно вызвать 'destroy()' до начала процесса. –

+1

Комментарий, который вы видите в 'destroy()', относится к другому сценарию. Этот комментарий относится к тому факту, что если процесс сам умирает, ОС может повторно использовать свой ПИД для какого-либо другого нового процесса, поэтому к моменту, когда наше приложение вызывает 'destroy()', PID может быть другим несвязанным процессом, который мы не хотят убивать. Код рядом с этим комментарием просто говорит: «Если процесс уже вышел сам по себе, не убивайте его», что является попыткой (а не на 100% надежной, как указано), чтобы избежать убийства неправильной вещи. –

2

Это просто потому, что до того, как потоки выполнят вызов уничтожения, ваша основная программа завершается и все связанные потоки покидают запущенные процессы. Чтобы убедиться в этом, просто добавьте вызов 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); 

} 

}

+0

Это не решает проблему; это на самом деле один из первых тестов, которые я тоже побежал. У меня был сон на 10 секунд, и за это время несколько процессов оставались бегущими во время всего сна, хотя они должны были быть уничтожены (и не прекращались, когда программа прекращалась). –

+0

Я запускаю вышеуказанный код в Debian, и после добавления Thread.sleep() он не имеет процессов. То, что я сделал выше, не является правильным способом дождаться остановки потоков. Вам нужно будет вызвать Thread.join для каждого из потоков, созданных в цикле в конце. Вы пробовали это, и это все та же проблема? – Raji

+0

Пробовал ваш код, проблема остается. Но я не вижу вашей точки зрения, проблема возникает не из потоков, а из процессов. Завершающий поток не завершает запущенные процессы. – Bastien

2

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

+0

Уже пробовал, не решает. – Bastien

+0

Это не обязательно, если вы посмотрите, например. при реализации 'UnixProcess # destroy()', он автоматически закрывает все потоки. – Mifeet

+0

@Mifeet - на этот вопрос был дан ответ 2 года назад. вы смотрите на производственную версию java от двух лет назад? (и этот опыт может быть с раннего jdk 6 или даже 5, жестким, чтобы держать их всех прямо в этот момент времени). – jtahlborn

0

У меня была очень похожая проблема, и проблема с 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(); 
Смежные вопросы