Java调用外部程序技巧

15 篇文章 0 订阅
12 篇文章 0 订阅

前些天使用Java调用外部程序的时候,发现线程会堵塞在waitfor()方法。
调用方法如下:

  1. Process  process =   Runtime. getRuntime ( ). exec (cmd ) ;
  2. process. waitfor ( ) ;

如果直接在Shell中调用这个程序,程序会很快结束,不会僵死。

为什么会堵塞呢,原因是当调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立3个管道连接,标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取,数据会暂时缓冲在Linux的缓冲区,缓冲区满后该程序将无法继续写数据,会僵死,所以Java程序就会僵死在waitfor(),永远无法结束。

解决办法就是增加两个线程,一个线程负责读标准输出流,另一个负责读标准错误流,这样子数据就不会积压在缓冲区,程序就能够顺利运行。

查看源代码后,还发现一个潜在的问题。但程序执行到exec的时候,JVM会使用管道,占有3个文件句柄,但程序运行结束后,这三个句柄并不会自动关闭,这样最终会导致java.io.IOException: Too many open files。所以就算外部程序的没有输出,也必须关闭句柄:

  1. Process  process= null ;
  2. try {
  3.   process =   Runtime. getRuntime ( ). exec (cmd ) ;
  4.   process. waitfor ( ) ;
  5. }cache {
  6.   process. getOutputStream ( ). close ( ) ;
  7.   process. getInputStream ( ). close ( ) ;
  8.   process. getErrorStream ( ). close ( ) ;
  9. }

我们发觉当调用close()方法后,JVM并不会立即回收句柄,具体的回收时间不确定。另外如果不调用close(),句柄也会被回收,也可能发生“Too many open files”的错误。根据这篇文章,不同的垃圾收集器会选择不同的回收策略。所以最好还是要关闭。

总结
1.如果外部程序有大量输出,需要启动额外的线程来读取标准输出和标准错误流
2.必须关闭三个句柄

 

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ProcessUtil {
	private static final int DEFAULT_BUFFER_SIZE = 1024;
	private static Log log = LogFactory.getLog(ProcessUtil.class);

	private InputStream in;

	private OutputStream out;
	private OutputStream err;

	public ProcessUtil(OutputStream out, OutputStream err, InputStream in) {
		if (out == null) {
			out = new NullOutputStream();
		}
		if (err == null) {
			err = new NullOutputStream();
		}
		this.out = out;
		this.err = err;
		this.in = in;

	}

	public int process(String cmd, String[] envp, File dir) throws IOException,
			InterruptedException {
		Process p = Runtime.getRuntime().exec(cmd, envp, dir);
		return process(p);
	}

	public int process(String[] cmdarray, String[] envp, File dir)
			throws IOException, InterruptedException {
		Process p = Runtime.getRuntime().exec(cmdarray, envp, dir);
		return process(p);
	}

	private int process(Process p) throws IOException, InterruptedException {
		try {
			OutputStream pin = p.getOutputStream();

			StreamGobbler outg = new StreamGobbler(p.getInputStream(), out);
			StreamGobbler errg = new StreamGobbler(p.getErrorStream(), err);

			outg.start();
			errg.start();

			if (in != null) {
				byte[] inBuf = new byte[DEFAULT_BUFFER_SIZE];
				int inN = 0;
				while (-1 != (inN = in.read(inBuf))) {
					pin.write(inBuf, 0, inN);
				}
				pin.flush();
			}

			int code = p.waitFor();
			outg.join();
			errg.join();
			return code;
		} finally {
			closeQuietly(p.getOutputStream());
			closeQuietly(p.getInputStream());
			closeQuietly(p.getErrorStream());
		}

	}

	private void closeQuietly(Closeable closeable) {
		try {
			if (closeable != null)
				closeable.close();
		} catch (Exception e) {
			log.warn("close error", e);
		}
	}

}

class StreamGobbler implements Runnable {
	private static final int DEFAULT_BUFFER_SIZE = 1024;
	private static Log log = LogFactory.getLog(StreamGobbler.class);

	private InputStream is;
	private OutputStream os;
	private Thread thread;

	public StreamGobbler(InputStream is, OutputStream os) {
		this.is = is;
		this.os = os;
	}

	public void start() {
		thread = new Thread(this);
		thread.setDaemon(true);
		thread.start();
	}

	public void run() {
		try {
			byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
			int n = 0;
			while (-1 != (n = is.read(buf))) {
				os.write(buf, 0, n);
			}
			os.flush();
		} catch (Exception ex) {
			log.warn("stream error", ex);
		}
	}

	public void join() throws InterruptedException {
		thread.join();
	}

}

class NullOutputStream extends OutputStream {

	public static final NullOutputStream NULL_OUTPUT_STREAM = new NullOutputStream();

	@Override
	public void write(byte[] b, int off, int len) {
	}

	@Override
	public void write(int b) {
	}

	@Override
	public void write(byte[] b) throws IOException {
	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值