从Java开始忽略/捕获子进程输出的最简单方法

发布于 2024-10-19 19:21:50 字数 469 浏览 1 评论 0 原文

java中的子进程非常昂贵。每个进程通常由多个线程支持。

  • 一个线程来承载进程(通过Linux上的JDK 1.6)
  • 一个线程来读取以读取/打印/忽略输入流
  • 另一个线程来读取/打印/忽略错误流
  • 更多线程来执行超时和监视并终止子进程由您的应用程序的
  • 业务逻辑线程,保持直到子流程返回。

如果你有一个线程池子进程来执行任务,线程的数量就会失控。因此,峰值时可能有超过两倍的并发线程。

在很多情况下,我们fork一个进程只是因为没有人能够编写JNI来调用JDK中缺少的本机函数(例如chmod,ln,ls),触发shell脚本等。

有些线程可以被保存,但有些线程应该运行以防止最坏的情况(输入流上的缓冲区溢出)。

如何将Java中创建子进程的开销降至最低? 我正在考虑 NIO 流处理、组合和共享线程、降低后台线程优先级、重用进程。但我不知道它们是否可能。

Sub-process in java are very expensive. Each process is usually support by a NUMBERS of threads.

  • a thread to host the process (by JDK 1.6 on linux)
  • a thread to read to read/print/ignore the input stream
  • another thread to read/print/ignore the error stream
  • a more thread to do timeout and monitoring and kill sub-process by your application
  • the business logic thread, holduntil for the sub-process return.

The number of thread get out of control if you have a pool of thread focking sub-process to do tasks. As a result, there may be more then a double of concurrent thread at peak.

In many cases, we fork a process just because nobody able to write JNI to call native function missing from the JDK (e.g. chmod, ln, ls), trigger a shell script, etc, etc.

Some thread can be saved, but some thread should run to prevent the worst case (buffer overrun on inputstream).

How can I reduce the overhead of creating sub-process in Java to the minimum?
I am thinking of NIO the stream handles, combine and share threads, lower background thread priority, re-use of process. But I have no idea are they possible or not.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

似梦非梦 2024-10-26 19:21:50

JDK7将解决这个问题并提供新的API redirectOutput/redirectError 重定向 stdout/stderr。

然而坏消息是他们忘记提供“Redirect.toNull”,这意味着你会想要做类似“if(*nix)/dev/null elsif(win)nil”的事情,

令人难以置信的是 NIO/2 api for Process 仍然丢失的;但我认为redirectOutput+NIO2的AsynchronizeChannel会有帮助。

JDK7 will address this issue and provide new API redirectOutput/redirectError in ProcessBuilder to redirect stdout/stderr.

However the bad news is that they forget to provide a "Redirect.toNull" what mean you will want to do something like "if(*nix)/dev/null elsif(win)nil"

Unbeliable that NIO/2 api for Process still missing; but I think redirectOutput+NIO2's AsynchronizeChannel will help.

谁与争疯 2024-10-26 19:21:50

我创建了一个开源库,允许 java 和子进程之间的非阻塞 I/O。该库提供了事件驱动的回调模型。它依赖于 JNA 库来使用特定于平台的本机 API,例如 Linux 上的 epoll、MacOS X 上的 kqueue/kevent 或 Windows 上的 IO Completion Ports。

该项目名为 NuProcess,可以在这里找到:

https://github.com/brettwooldridge/NuProcess

I have created an open source library that allows non-blocking I/O between java and your child processes. The library provides an event-driven callback model. It depends on the JNA library to use platform-specific native APIs, such as epoll on Linux, kqueue/kevent on MacOS X, or IO Completion Ports on Windows.

The project is called NuProcess and can be found here:

https://github.com/brettwooldridge/NuProcess

↙温凉少女 2024-10-26 19:21:50

要回答您的主题(我不明白描述),我假设您的意思是 shell 子进程输出,请检查这些 SO 问题:

Java 的平台独立 /dev/null 输出接收器

Java 中是否存在 Null OutputStream?

或者您可以关闭 Unix 下正在执行的命令的 stdout 和 stderr:

command > /dev/null 2>&1

To answer your topic (I don't understand description), I assume you mean shell subprocess output, check these SO issues:

platform-independent /dev/null output sink for Java

Is there a Null OutputStream in Java?

Or you can close stdout and stderr for the command being executed under Unix:

command > /dev/null 2>&1
剧终人散尽 2024-10-26 19:21:50

您不需要任何额外的线程来在 java 中运行子进程,尽管处理超时确实使事情变得有点复杂:

import java.io.IOException;
import java.io.InputStream;

public class ProcessTest {

  public static void main(String[] args) throws IOException {
    long timeout = 10;

    ProcessBuilder builder = new ProcessBuilder("cmd", "a.cmd");
    builder.redirectErrorStream(true); // so we can ignore the error stream
    Process process = builder.start();
    InputStream out = process.getInputStream();

    long endTime = System.currentTimeMillis() + timeout;

    while (isAlive(process) && System.currentTimeMillis() < endTime) {
      int n = out.available();
      if (n > 0) {
        // out.skip(n);
        byte[] b = new byte[n];
        out.read(b, 0, n);
        System.out.println(new String(b, 0, n));
      }

      try {
        Thread.sleep(10);
      }
      catch (InterruptedException e) {
      }
    }

    if (isAlive(process)) {
      process.destroy();
      System.out.println("timeout");
    }
    else {
      System.out.println(process.exitValue());
    }
  }

  public static boolean isAlive(Process p) {
    try {
      p.exitValue();
      return false;
    }
    catch (IllegalThreadStateException e) {
      return true;
    }
  }
}

您也可以使用反射,如 是否可以从具有超时的输入流中读取? 获取 NIO Process.getInputStream() 的 >FileChannel,但是您必须担心不同的 JDK 版本以换取摆脱轮询。

You don't need any extra threads to run a subprocess in java, although handling timeouts does complicate things a bit:

import java.io.IOException;
import java.io.InputStream;

public class ProcessTest {

  public static void main(String[] args) throws IOException {
    long timeout = 10;

    ProcessBuilder builder = new ProcessBuilder("cmd", "a.cmd");
    builder.redirectErrorStream(true); // so we can ignore the error stream
    Process process = builder.start();
    InputStream out = process.getInputStream();

    long endTime = System.currentTimeMillis() + timeout;

    while (isAlive(process) && System.currentTimeMillis() < endTime) {
      int n = out.available();
      if (n > 0) {
        // out.skip(n);
        byte[] b = new byte[n];
        out.read(b, 0, n);
        System.out.println(new String(b, 0, n));
      }

      try {
        Thread.sleep(10);
      }
      catch (InterruptedException e) {
      }
    }

    if (isAlive(process)) {
      process.destroy();
      System.out.println("timeout");
    }
    else {
      System.out.println(process.exitValue());
    }
  }

  public static boolean isAlive(Process p) {
    try {
      p.exitValue();
      return false;
    }
    catch (IllegalThreadStateException e) {
      return true;
    }
  }
}

You could also play with reflection as in Is it possible to read from a InputStream with a timeout? to get a NIO FileChannel from Process.getInputStream(), but then you'd have to worry about different JDK versions in exchange for getting rid of the polling.

姜生凉生 2024-10-26 19:21:50

nio 不起作用,因为当您创建进程时,您只能访问 OutputStream,而不能访问 Channel。

您可以让 1 个线程读取多个 InputStream。

例如,

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class MultiSwallower implements Runnable {    
   private List<InputStream> streams = new CopyOnWriteArrayList<InputStream>();
   public void addStream(InputStream s) {
       streams.add(s);
   }

   public void removeStream(InputStream s) {
       streams.remove(s);
   }

    public void run() {
        byte[] buffer = new byte[1024];
        while(true) {

          boolean sleep = true;
          for(InputStream s : streams) {
              //available tells you how many bytes you can read without blocking
              while(s.available() > 0) {
                  //do what you want with the output here
                  s.read(buffer, 0, Math.min(s.available(), 1024));
                  sleep = false;
              }   
          }
          if(sleep) {
              //if nothing is available now
              //sleep 
              Thread.sleep(50);
          }
        }

    }
}

您可以将上述类与等待进程完成的另一个类配对,例如,

class ProcessWatcher implements Runnable {

    private MultiSwallower swallower = new MultiSwallower();

    private ConcurrentMap<Process, InputStream> proceses = new ConcurrentHashMap<Process, InputStream>();

    public ProcessWatcher() {

    } 

    public void startThreads() { 
        new Thread(this).start();
        new Thread(swallower).start();
    }


    public void addProcess(Process p) {
        swallower.add(p.getInputStream());
        proceses.put(p, p.getInputStream());

    }

    @Override
    public void run() {
        while(true) {

            for(Process p : proceses.keySet()) {
                try {
                    //will throw if the process has not completed
                    p.exitValue();
                    InputStream s = proceses.remove(p);
                    swallower.removeStream(s);
                } catch(IllegalThreadStateException e) { 
                    //process not completed, ignore
                }
            }
            //wait before checking again
            Thread.sleep(50);
        }
    }
}

如果您使用 ProcessBuilder.redirectErrorStream(true),则不需要为每个错误流拥有 1 个线程,并且您不需要 1 个线程来读取进程输入流,如果您不向输入流写入任何内容,则可以简单地忽略该输入流。

nio won't work, since when you create a process you can only access the OutputStream, not a Channel.

You can have 1 thread read multiple InputStreams.

Something like,

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class MultiSwallower implements Runnable {    
   private List<InputStream> streams = new CopyOnWriteArrayList<InputStream>();
   public void addStream(InputStream s) {
       streams.add(s);
   }

   public void removeStream(InputStream s) {
       streams.remove(s);
   }

    public void run() {
        byte[] buffer = new byte[1024];
        while(true) {

          boolean sleep = true;
          for(InputStream s : streams) {
              //available tells you how many bytes you can read without blocking
              while(s.available() > 0) {
                  //do what you want with the output here
                  s.read(buffer, 0, Math.min(s.available(), 1024));
                  sleep = false;
              }   
          }
          if(sleep) {
              //if nothing is available now
              //sleep 
              Thread.sleep(50);
          }
        }

    }
}

You can pair the above class with another class that waits for the Processes to complete, something like,

class ProcessWatcher implements Runnable {

    private MultiSwallower swallower = new MultiSwallower();

    private ConcurrentMap<Process, InputStream> proceses = new ConcurrentHashMap<Process, InputStream>();

    public ProcessWatcher() {

    } 

    public void startThreads() { 
        new Thread(this).start();
        new Thread(swallower).start();
    }


    public void addProcess(Process p) {
        swallower.add(p.getInputStream());
        proceses.put(p, p.getInputStream());

    }

    @Override
    public void run() {
        while(true) {

            for(Process p : proceses.keySet()) {
                try {
                    //will throw if the process has not completed
                    p.exitValue();
                    InputStream s = proceses.remove(p);
                    swallower.removeStream(s);
                } catch(IllegalThreadStateException e) { 
                    //process not completed, ignore
                }
            }
            //wait before checking again
            Thread.sleep(50);
        }
    }
}

As well, you don't need to have 1 thread for each error stream if you use ProcessBuilder.redirectErrorStream(true), and you don't need 1 thread for reading the process input stream, you can simply ignore the input stream if you are not writing anything to it.

背叛残局 2024-10-26 19:21:50

既然您提到了 chmod、ln、ls 和 shell 脚本,听起来您正在尝试使用 Java 进行 shell 编程。如果是这样,您可能需要考虑使用更适合该任务的其他语言,例如 Python、Perl 或 Bash。虽然当然可以在 Java 中创建子进程,通过标准输入/输出/错误流等与它们交互,但我认为您会发现脚本语言使此类代码比 Java 更简洁且更易于维护。

Since you mention, chmod, ln, ls, and shell scripts, it sounds like you're trying to use Java for shell programming. If so, you might want to consider a different language that is better suited to that task such as Python, Perl, or Bash. Although it's certainly possible to create subprocesses in Java, interact with them via their standard input/output/error streams, etc., I think you will find a scripting language makes this kind of code less verbose and easier to maintain than Java.

夕色琉璃 2024-10-26 19:21:50

您是否考虑过使用用另一种语言(可能是 shell 脚本?)编写的单个长时间运行的帮助程序进程,该进程将通过 stdin 使用来自 java 的命令并执行文件操作作为响应?

Have you considered using a single long-running helper process written in another language (maybe a shell script?) that will consume commands from java via stdin and perform file operations in response?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文