【问题标题】:process.waitFor() never returnsprocess.waitFor() 永远不会返回
【发布时间】:2025-12-27 22:10:16
【问题描述】:
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();

【问题讨论】:

  • 请注意,在 JAVA 8 上有一个 waitFor 重载,让您指定指定超时。这可能是避免 waitFor 永远不会返回的情况的更好选择。
  • 在我的情况下,我在读取输出流之前添加了 waitFor() 并导致死锁情况。if(!process.waitFor(15, TimeUnit.MINUTES)) { process.destroy(); } else { process.getOutputStream().close(); BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));

标签: java runtime.exec


【解决方案1】:

waitFor() 没有返回的原因有很多。

但通常归结为执行的命令没有退出。

同样,这可能有很多原因。

一个常见的原因是该过程产生了一些输出,而您没有从适当的流中读取。这意味着一旦缓冲区已满,该进程就会被阻止并等待您的进程继续读取。您的进程反过来等待另一个进程完成(它不会因为它等待您的进程,...)。这是典型的死锁情况。

您需要不断地从进程输入流中读取,以确保它不会阻塞。

有一篇很好的文章解释了Runtime.exec() 的所有缺陷并展示了绕过它们的方法,称为"When Runtime.exec() won't"(是的,这篇文章来自 2000 年,但内容仍然适用!)

【讨论】:

  • 这个答案是正确的,但它错过了解决问题的代码示例。查看 Peter Lawrey 的有用代码答案,了解为什么 waitFor() 没有返回。
  • 如果进程不应该退出?如果你运行ls,它会退出,但如果你启动httpd呢?
  • @d-b:我不确定你的问题是什么。是的,这就是waitFor()不返回的另一个原因。
  • 问题是:您如何处理这种情况?您希望 httpd 或其他任何东西保持打开状态。
  • @d-b:在这种情况下,根本不要调用 waitFor()。
【解决方案2】:

在等待输出完成之前,您似乎没有读取输出。仅当输出未填充缓冲区时才可以。如果是,它会等到您读取输出,catch-22。

也许您有一些您没有阅读的错误。这将导致应用程序停止并 waitFor 永远等待。解决此问题的一种简单方法是将错误重定向到常规输出。

ProcessBuilder pb = new ProcessBuilder("tasklist");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null)
    System.out.println("tasklist: " + line);
process.waitFor();

【讨论】:

  • 供参考:ProcessBuilder是一个真正的builder,你可以直接写ProcessBuilder pb = new ProcessBuilder("tasklist").redirectErrorStream(true);
  • 我宁愿使用pb.redirectError(new File("/dev/null"));
  • @Toochka 仅供参考,redirectError 仅在 Java 1.7 后可用
  • 我相信应该是公认的答案,我用这个替换了我的代码,它立即起作用了。
  • 谢谢@Peter。 pb.redirectErrorStream(true); 解决了我的问题。
【解决方案3】:

同样来自 Java 文档:

java.lang

上课过程

因为一些原生平台只为标准输入提供有限的缓冲区大小,并且 输出流,未能及时写入输入流或读取输出流 子进程可能会导致子进程阻塞,甚至死锁。

无法清除输入流的缓冲区(通过管道连接到子进程的输出流) from Process 可能会导致子进程阻塞。

试试这个:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

【讨论】:

  • 两个警告:(1)使用 ProcessBuilder + redirectErrorStream(true),那么你是安全的。否则,(2) 您需要一个线程从 Process.getInputStream() 中读取,而另一个线程从 Process.getErrorStream() 中读取。只花了大约四个小时来解决这个问题(!),也就是“艰难之路”。
  • 您可以使用 Apache Commons Exec 功能同时使用 stdout 和 stderr 流:DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);,其中 stoutOS 和 stderrOS 是 BufferedOutputStreams 我创建的用于写入适当文件的。跨度>
  • 在我的例子中,我从 Spring 调用了一个批处理文件,该文件在内部打开了一个编辑器。即使应用了process.close() 的代码,我的代码仍然挂起。但是当我按照上面的建议打开输入流并立即关闭时——问题就消失了。所以在我的例子中,Spring 正在等待流关闭信号。即使我使用的是 Java 8 自动关闭。
  • @kevinarpe 您可以使用InputStream.available() 从单个线程消耗两个流而不会阻塞。只要该方法返回一个正数,就可以保证读取不会阻塞。
【解决方案4】:

我想在之前的答案中添加一些内容,但由于我没有代表发表评论,所以我只会添加一个答案。这是针对使用 Java 编程的 android 用户。

根据 RollingBoy 的帖子,这段代码几乎对我有用:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

在我的例子中,waitFor() 没有释放,因为我正在执行一个没有返回的语句(“ip adddr flush eth0”)。解决此问题的一种简单方法是确保您始终在语句中返回某些内容。对我来说,这意味着执行以下命令:“ip adddr flush eth0 && echo done”。您可以整天读取缓冲区,但如果没有返回任何内容,您的线程将永远不会释放它的等待。

希望对某人有所帮助!

【讨论】:

  • 当您没有代表发表评论时,不要解决它并发表评论。将其作为自己的答案并从中获得代表!
  • 我不认为是process.waitFor() 挂起它是reader.readLine() 挂起如果你没有输出。如果出现问题,我尝试使用waitFor(long,TimeUnit) 超时,并发现它是读取挂起的。这使得超时版本需要另一个线程来读取......
【解决方案5】:

有几种可能:

  1. 您尚未消耗进程stdout 上的所有输出。
  2. 您尚未消耗进程stderr 上的所有输出。
  3. 该进程正在等待您的输入,而您尚未提供它,或者您尚未关闭该进程的stdin
  4. 进程在硬循环中旋转。

【讨论】:

    【解决方案6】:

    正如其他人所说,您必须使用 stderrstdout

    与其他答案相比,从 Java 1.7 开始,它更加容易。您不必再自己创建线程来读取 stderrstdout

    只需使用ProcessBuilder 并将redirectOutput 方法与redirectErrorredirectErrorStream 结合使用。

    String directory = "/working/dir";
    File out = new File(...); // File to write stdout to
    File err = new File(...); // File to write stderr to
    ProcessBuilder builder = new ProcessBuilder();
    builder.directory(new File(directory));
    builder.command(command);
    builder.redirectOutput(out); // Redirect stdout to file
    if(out == err) { 
      builder.redirectErrorStream(true); // Combine stderr into stdout
    } else { 
      builder.redirectError(err); // Redirect stderr to file
    }
    Process process = builder.start();
    

    【讨论】:

      【解决方案7】:

      出于同样的原因,您还可以使用 inheritIO() 将 Java 控制台与外部应用程序控制台映射,例如:

      ProcessBuilder pb = new ProcessBuilder(appPath, arguments);
      
      pb.directory(new File(appFile.getParent()));
      pb.inheritIO();
      
      Process process = pb.start();
      int success = process.waitFor();
      

      【讨论】:

        【解决方案8】:

        你应该尝试同时消耗输出和错误

            private void runCMD(String CMD) throws IOException, InterruptedException {
            System.out.println("Standard output: " + CMD);
            Process process = Runtime.getRuntime().exec(CMD);
        
            // Get input streams
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
            BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String line = "";
            String newLineCharacter = System.getProperty("line.separator");
        
            boolean isOutReady = false;
            boolean isErrorReady = false;
            boolean isProcessAlive = false;
        
            boolean isErrorOut = true;
            boolean isErrorError = true;
        
        
            System.out.println("Read command ");
            while (process.isAlive()) {
                //Read the stdOut
        
                do {
                    isOutReady = stdInput.ready();
                    //System.out.println("OUT READY " + isOutReady);
                    isErrorOut = true;
                    isErrorError = true;
        
                    if (isOutReady) {
                        line = stdInput.readLine();
                        isErrorOut = false;
                        System.out.println("=====================================================================================" + line + newLineCharacter);
                    }
                    isErrorReady = stdError.ready();
                    //System.out.println("ERROR READY " + isErrorReady);
                    if (isErrorReady) {
                        line = stdError.readLine();
                        isErrorError = false;
                        System.out.println("ERROR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" + line + newLineCharacter);
        
                    }
                    isProcessAlive = process.isAlive();
                    //System.out.println("Process Alive " + isProcessAlive);
                    if (!isProcessAlive) {
                        System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Process DIE " + line + newLineCharacter);
                        line = null;
                        isErrorError = false;
                        process.waitFor(1000, TimeUnit.MILLISECONDS);
                    }
        
                } while (line != null);
        
                //Nothing else to read, lets pause for a bit before trying again
                System.out.println("PROCESS WAIT FOR");
                process.waitFor(100, TimeUnit.MILLISECONDS);
            }
            System.out.println("Command finished");
        }
        

        【讨论】:

          【解决方案9】:

          我想我发现了一个类似的问题:一些进程已启动,似乎运行成功但从未完成。函数 waitFor() 一直在等待,除非我在任务管理器中终止了该进程。
          但是,在命令行长度为 127 个字符或更短的情况下,一切都运行良好。如果长文件名是不可避免的,您可能需要使用环境变量,这可以让您保持命令行字符串简短。您可以生成一个批处理文件(使用 FileWriter),您可以在其中设置环境变量,然后再调用您实际要运行的程序。 此类批次的内容可能如下所示:

              set INPUTFILE="C:\Directory 0\Subdirectory 1\AnyFileName"
              set OUTPUTFILE="C:\Directory 2\Subdirectory 3\AnotherFileName"
              set MYPROG="C:\Directory 4\Subdirectory 5\ExecutableFileName.exe"
              %MYPROG% %INPUTFILE% %OUTPUTFILE%
          

          最后一步是使用 Runtime 运行这个批处理文件。

          【讨论】:

            【解决方案10】:

            这是一种适合我的方法。 注意:此方法中的某些代码可能不适用于您,因此请尝试忽略它。例如“logStandardOut(...)、git-bash 等”。

            private String exeShellCommand(String doCommand, String inDir, boolean ignoreErrors) {
            logStandardOut("> %s", doCommand);
            
            ProcessBuilder builder = new ProcessBuilder();
            StringBuilder stdOut = new StringBuilder();
            StringBuilder stdErr = new StringBuilder();
            
            boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
            if (isWindows) {
              String gitBashPathForWindows = "C:\\Program Files\\Git\\bin\\bash";
              builder.command(gitBashPathForWindows, "-c", doCommand);
            } else {
              builder.command("bash", "-c", doCommand);
            }
            
            //Do we need to change dirs?
            if (inDir != null) {
              builder.directory(new File(inDir));
            }
            
            //Execute it
            Process process = null;
            BufferedReader brStdOut;
            BufferedReader brStdErr;
            try {
              //Start the command line process
              process = builder.start();
            
              //This hangs on a large file
              // https://*.com/questions/5483830/process-waitfor-never-returns
              //exitCode = process.waitFor();
            
              //This will have both StdIn and StdErr
              brStdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
              brStdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            
              //Get the process output
              String line = null;
              String newLineCharacter = System.getProperty("line.separator");
            
              while (process.isAlive()) {
                //Read the stdOut
                while ((line = brStdOut.readLine()) != null) {
                  stdOut.append(line + newLineCharacter);
                }
            
                //Read the stdErr
                while ((line = brStdErr.readLine()) != null) {
                  stdErr.append(line + newLineCharacter);
                }
            
                //Nothing else to read, lets pause for a bit before trying again
                process.waitFor(100, TimeUnit.MILLISECONDS);
              }
            
              //Read anything left, after the process exited
              while ((line = brStdOut.readLine()) != null) {
                stdOut.append(line + newLineCharacter);
              }
            
              //Read anything left, after the process exited
              while ((line = brStdErr.readLine()) != null) {
                stdErr.append(line + newLineCharacter);
              }
            
              //cleanup
              if (brStdOut != null) {
                brStdOut.close();
              }
            
              if (brStdErr != null) {
                brStdOut.close();
              }
            
              //Log non-zero exit values
              if (!ignoreErrors && process.exitValue() != 0) {
                String exMsg = String.format("%s%nprocess.exitValue=%s", stdErr, process.exitValue());
                throw new ExecuteCommandException(exMsg);
              }
            
            } catch (ExecuteCommandException e) {
              throw e;
            } catch (Exception e) {
              throw new ExecuteCommandException(stdErr.toString(), e);
            } finally {
              //Log the results
              logStandardOut(stdOut.toString());
              logStandardError(stdErr.toString());
            }
            
            return stdOut.toString();
            

            }

            【讨论】:

              【解决方案11】:

              流的异步读取结合避免等待超时将解决问题。

              你可以在这里找到解释这个的页面http://simplebasics.net/.net/process-waitforexit-with-a-timeout-will-not-be-able-to-collect-the-output-message/

              【讨论】: