从Java开始忽略/捕获子进程输出的最简单方法
Posted
技术标签:
【中文标题】从Java开始忽略/捕获子进程输出的最简单方法【英文标题】:The lightest way to ignore/capture output of sub-process start from Java 【发布时间】:2011-07-07 07:12:13 【问题描述】:Java 中的子进程非常昂贵。每个进程通常由 NUMBERS 个线程支持。
一个线程来承载进程(通过 JDK 1.6 on linux) 读取/打印/忽略输入流的线程 另一个线程读取/打印/忽略错误流 更多线程来执行超时以及监视和终止您的应用程序的子进程 业务逻辑线程,holduntil为子流程返回。如果你有一个线程池子进程来执行任务,线程的数量就会失控。因此,在峰值时可能会有超过两倍的并发线程。
在很多情况下,我们 fork 一个进程只是因为没有人能够编写 JNI 来调用 JDK 中缺少的本机函数(例如 chmod、ln、ls)、触发 shell 脚本等。
可以保存一些线程,但应该运行一些线程以防止最坏的情况(输入流上的缓冲区溢出)。
如何将在 Java 中创建子进程的开销降到最低? 我正在考虑 NIO 流处理、组合和共享线程、降低后台线程优先级、重用进程。但我不知道它们是否可能。
【问题讨论】:
为什么这(大量线程)是个问题?这些线程中的大多数都会“等待”某些东西,例如等待子进程写入的东西。所以这些线程不应该消耗任何 CPU。 高线程数是有效的。在内存方面 - 每个线程分配它自己的堆栈。在 CPU 方面 - 上下文切换会降低您的 CPU 能力。而且每个线程都带有锁,这是程序员必须关心的对象。 【参考方案1】:我创建了一个开源库,它允许在 java 和您的子进程之间进行非阻塞 I/O。该库提供了一个事件驱动的回调模型。它依赖于 JNA 库来使用特定平台的原生 API,例如 Linux 上的 epoll、MacOS X 上的 kqueue/kevent 或 Windows 上的 IO Completion Ports。
该项目名为NuProcess,可在此处找到:
https://github.com/brettwooldridge/NuProcess
【讨论】:
【参考方案2】:JDK7 将解决这个问题,并在 ProcessBuilder 中提供新的 API redirectOutput/redirectError 来重定向 stdout/stderr。
但坏消息是他们忘记提供“Redirect.toNull”,这意味着您将要执行“if(*nix)/dev/null elsif(win)nil”之类的操作
令人难以置信的是,仍然缺少 NIO/2 api for Process;但我认为 redirectOutput+NIO2 的 AsynchronizeChannel 会有所帮助。
【讨论】:
添加了 Java 9ProcessBuilder.Redirect.DISCARD
。【参考方案3】:
您是否考虑过使用用另一种语言(可能是 shell 脚本?)编写的单个长时间运行的帮助进程,该进程将通过标准输入使用来自 java 的命令并执行文件操作作为响应?
【讨论】:
它让事情变得复杂,难以调试,几乎无法做多线程【参考方案4】: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 个线程来读取流程输入流,您可以简单地忽略输入如果您没有向其写入任何内容,请流式传输。
【讨论】:
我以前也想过。但它有一个很大的错误——它不会关闭流也不会释放 GC。可能需要额外使用 SoftReference 或最终进行垃圾引用收集。对于应用程序中的小工具来说,有些东西太多了。 让我想想......我必须从你的代码中改进的是 - 1. Weak/SoftReference; 2.一个并发锁来代替Thread.sleep; 3. 使用 AOP 或模式来捕获 waitFor() 和 destory()。 4. 确保速度很快。 5. 提交给Apache Common-IO、Google-IO、OpenJDK,让他们做维护。 StreamSwallower 类不会自行关闭流,我在答案中添加了一个 ProcessWatcher 类以显示您将如何管理进程/流。我认为不需要弱/软引用。使用上面的代码,您有 2 个线程管理许多进程。【参考方案5】:您不需要任何额外的线程来在 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;
您也可以像在Is it possible to read from a InputStream with a timeout? 中那样使用反射来从Process.getInputStream()
获得一个NIO FileChannel
,但是您必须担心不同的JDK 版本以换取摆脱轮询。
【讨论】:
【参考方案6】:为了回答你的话题(我不明白描述),我假设你的意思是 shell 子进程输出,检查这些 SO 问题:
platform-independent /dev/null output sink for Java
Is there a Null OutputStream in Java?
或者你可以关闭 Unix 下正在执行的命令的 stdout 和 stderr:
command > /dev/null 2>&1
【讨论】:
听起来很棒,但实际上它创造了更多。 ">" 不是一个有效的命令,而是 bash/cmd 的一个 shell 功能。您必须为目标进程的输出重定向启动一个包装器 bash 进程。 对,您可以更轻松地创建网关 bash 脚本,该脚本接收带有参数的命令并处理输出。【参考方案7】:既然你提到了chmod
、ln
、ls
和 shell 脚本,听起来你正在尝试使用 Java 进行 shell 编程。如果是这样,您可能需要考虑另一种更适合该任务的语言,例如 Python、Perl 或 Bash。虽然当然可以在 Java 中创建子进程,通过它们的标准输入/输出/错误流等与它们进行交互,但我认为您会发现脚本语言使这种代码比 Java 更简洁且更易于维护。
【讨论】:
这些脚本语言也有针对 JVM 的实现,可以方便与现有 Java 代码的集成 没有。我不是。它们是根据应用程序的需要调用的,而不是 shell 编程。以上是关于从Java开始忽略/捕获子进程输出的最简单方法的主要内容,如果未能解决你的问题,请参考以下文章
如何捕获从 python 子进程运行的 git clone 命令的输出