Runtime.exec ()가 실행되지 않을 때

Java 언어의 일부로 java.lang패키지는 암시 적으로 모든 Java 프로그램으로 가져옵니다. 이 패키지의 함정은 종종 드러나며 대부분의 프로그래머에게 영향을 미칩니다. 이번 달에는 Runtime.exec()방법에 숨어있는 함정에 대해 논의 할 것 입니다.

함정 4 : Runtime.exec ()가 실행되지 않을 때

이 클래스 java.lang.Runtime에는 getRuntime()현재 Java Runtime Environment를 검색하는 라는 정적 메서드가 있습니다. 이것이 Runtime객체에 대한 참조를 얻는 유일한 방법 입니다. 이 참조를 사용하면 Runtime클래스의 exec()메서드를 호출하여 외부 프로그램을 실행할 수 있습니다 . 개발자는 종종이 메소드를 호출하여 HTML로 도움말 페이지를 표시하기위한 브라우저를 실행합니다.

다음과 같은 네 가지 오버로드 된 버전의 exec()명령이 있습니다.

  • public Process exec(String command);
  • public Process exec(String [] cmdArray);
  • public Process exec(String command, String [] envp);
  • public Process exec(String [] cmdArray, String [] envp);

이러한 각 메서드에 대해 명령 및 인수 집합이 운영 체제 별 함수 호출에 전달됩니다. 그러면 ProcessJava VM에 반환 된 클래스에 대한 참조가있는 운영 체제 별 프로세스 (실행중인 프로그램)가 생성 됩니다. Process의 특정 서브 클래스가 있기 때문에 클래스는 추상 클래스입니다 Process각 운영 체제에 존재한다.

다음 메소드에 세 가지 가능한 입력 매개 변수를 전달할 수 있습니다.

  1. 실행할 프로그램과 해당 프로그램에 대한 인수를 모두 나타내는 단일 문자열
  2. 프로그램과 인수를 구분하는 문자열 배열
  3. 환경 변수의 배열

환경 변수를 양식으로 전달하십시오 name=value. exec()프로그램과 인수 모두에 대해 단일 문자열로 의 버전을 사용하는 경우 해당 문자열은 StringTokenizer클래스 를 통해 구분 기호로 공백을 사용하여 구문 분석됩니다 .

IllegalThreadStateException으로 넘어짐

와 관련된 첫 번째 함정 Runtime.exec()IllegalThreadStateException. API의 일반적인 첫 번째 테스트는 가장 분명한 방법을 코딩하는 것입니다. 예를 들어 Java VM 외부에있는 프로세스를 실행하려면 exec()메서드를 사용합니다 . 외부 프로세스가 반환하는 값을보기 exitValue()위해 Process클래스 에서 메서드를 사용합니다 . 첫 번째 예에서는 Java 컴파일러 ( javac.exe) 를 실행합니다 .

목록 4.1 BadExecJavac.java

import java.util. *; import java.io. *; public class BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); 프로세스 proc = rt.exec ( "javac"); int exitVal = proc.exitValue (); System.out.println ( "Process exitValue :"+ exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

일련의 BadExecJavac생산물 :

E : \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException : 프로세스가 BadExecJavac.main (BadExecJavac.java:13)의 java.lang.Win32Process.exitValue (Native Method)에서 종료되지 않았습니다. 

외부 프로세스가 아직 완료되지 않은 경우 exitValue()메서드는 IllegalThreadStateException; 이것이이 프로그램이 실패한 이유입니다. 문서에이 사실이 명시되어 있지만이 방법이 유효한 답을 줄 때까지 기다릴 수없는 이유는 무엇입니까?

Process클래스 에서 사용할 수있는 메서드를 더 자세히 살펴보면 waitFor()정확하게이를 수행 하는 메서드를 알 수 있습니다. 사실, waitFor()또한 종료 값, 사용하지 않을 것이라고 수단 반환 exitValue()waitFor()하나 또는 다른를 선택할 것보다는 서로 함께 만합니다. exitValue()대신 사용할 수있는 유일한 시간은 waitFor()프로그램이 완료되지 않을 수있는 외부 프로세스에서 대기하는 것을 차단하지 않도록 할 때입니다. 대신 사용하는 waitFor()방법을, 나는라는 부울 매개 변수 전달 선호 waitForexitValue()현재의 thread를 대기해야하는지 여부를 결정하는 방법. 부울은 더 유익 할 것입니다.exitValue()이 메서드에 더 적합한 이름이며 두 메서드가 서로 다른 조건에서 동일한 기능을 수행 할 필요는 없습니다. 이러한 간단한 조건 식별은 입력 매개 변수의 영역입니다.

따라서이 트랩을 방지하려면를 포착 IllegalThreadStateException하거나 프로세스가 완료 될 때까지 기다리십시오.

이제 목록 4.1의 문제를 수정하고 프로세스가 완료 될 때까지 기다립니다. 목록 4.2에서 프로그램은 다시 실행을 시도한 javac.exe다음 외부 프로세스가 완료 될 때까지 기다립니다.

목록 4.2 BadExecJavac2.java

import java.util. *; import java.io. *; public class BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); 프로세스 proc = rt.exec ( "javac"); int exitVal = proc.waitFor (); System.out.println ( "Process exitValue :"+ exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

불행히도, 실행은 BadExecJavac2출력 을 생성하지 않습니다. 프로그램이 중단되고 완료되지 않습니다. javac프로세스가 완료 되지 않는 이유는 무엇 입니까?

Runtime.exec ()가 중단되는 이유

JDK의 Javadoc 문서는이 질문에 대한 답을 제공합니다.

일부 네이티브 플랫폼은 표준 입력 및 출력 스트림에 제한된 버퍼 크기 만 제공하기 때문에 입력 스트림을 즉시 쓰거나 하위 프로세스의 출력 스트림을 읽지 못하면 하위 프로세스가 차단되고 교착 상태가 될 수도 있습니다.

자주 인용되는 조언 인 RTFM (정밀 매뉴얼 읽기)에 암시 된 것처럼 프로그래머가 문서를 읽지 않는 경우 일 뿐입니 까? 대답은 부분적으로 그렇습니다. 이 경우 Javadoc을 읽으면 중간에 도달 할 수 있습니다. 외부 프로세스에 대한 스트림을 처리해야한다고 설명하지만 방법을 알려주지는 않습니다.

뉴스 그룹에서이 API에 관한 수많은 프로그래머 질문과 오해에서 알 수 있듯이 또 다른 변수가 여기에 있습니다.하지만 Runtime.exec()Process API는 매우 단순 해 보이지만 API의 단순하거나 명백한 사용 때문에 단순함이 속이는 것입니다. 오류가 발생하기 쉽습니다. API 디자이너를위한 교훈은 간단한 작업을 위해 간단한 API를 예약하는 것입니다. 복잡성과 플랫폼 별 종속성이 발생하기 쉬운 작업은 도메인을 정확하게 반영해야합니다. 추상화가 너무 멀리 전달 될 수 있습니다. JConfig라이브러리가 핸들 파일 및 프로세스 운영에 더 완전한 API의 예를 제공합니다 (자세한 내용은 아래 참고 자료 참조).

이제 JDK 문서를 따라 javac프로세스 의 출력을 처리하겠습니다 . 당신이 실행할 때 javac인수없이, 그것은 프로그램 및 사용 가능한 프로그램 옵션 모두의 의미를 실행하는 방법에 대해 설명합니다 사용하는 일련의 명령문을 생성합니다. 이것이 stderr스트림 으로 이동한다는 것을 알면 프로세스가 종료되기를 기다리기 전에 해당 스트림을 소진하는 프로그램을 쉽게 작성할 수 있습니다. 목록 4.3은 해당 작업을 완료합니다. 이 접근 방식은 작동하지만 좋은 일반적인 솔루션은 아닙니다. 따라서 목록 4.3의 프로그램 이름은 MediocreExecJavac; 평범한 해결책만을 제공합니다. 더 나은 솔루션은 표준 오류 스트림과 표준 출력 스트림을 모두 비우는 것입니다. 그리고 가장 좋은 해결책은 이러한 스트림을 동시에 비우는 것입니다 (나중에 설명하겠습니다).

목록 4.3 MediocreExecJavac.java

import java.util. *; import java.io. *; public class MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); 프로세스 proc = rt.exec ( "javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = 새로운 InputStreamReader (stderr); BufferedReader br = 새로운 BufferedReader (isr); 문자열 줄 = null; System.out.println ( ""); while ((line = br.readLine ())! = null) System.out.println (line); System.out.println ( ""); int exitVal = proc.waitFor (); System.out.println ( "Process exitValue :"+ exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

일련의 MediocreExecJavac생성 :

E : \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac 사용법 : javac where includes : -g 모든 디버깅 정보 생성 -g : none 디버깅 정보 생성 안 함 -g : {lines, vars, source} 일부 디버깅 정보 만 생성 -O 최적화; 디버깅을 방해하거나 클래스 파일을 확대 할 수 있습니다. -nowarn 경고를 생성하지 않습니다. -verbose 컴파일러가 수행하는 작업에 대한 출력 메시지 -deprecation 사용되지 않는 API가 사용되는 출력 소스 위치 -classpath 사용자 클래스 파일을 찾을 위치 지정 -sourcepath 입력 소스 파일을 찾을 위치 지정 -bootclasspath 부트 스트랩 클래스 파일의 위치 재정의 -extdirs 설치된 확장의 위치 재정의 -d 생성 된 클래스 파일을 배치 할 위치 지정 -encoding 소스 파일에 사용되는 문자 인코딩 지정 -target 특정 VM 버전에 대한 클래스 파일 생성 프로세스 exitValue : 2

따라서 MediocreExecJavac작동하고 종료 값 2. 일반적으로 종료 값은 0성공 을 나타냅니다. 0이 아닌 값은 오류를 나타냅니다. 이러한 종료 ​​값의 의미는 특정 운영 체제에 따라 다릅니다. 값이있는 Win32 오류 2는 "파일을 찾을 수 없음"오류입니다. 이는 javac우리가 컴파일 할 소스 코드 파일과 함께 프로그램을 따르기를 기대하기 때문 입니다.

따라서 Runtime.exec()시작하는 프로그램이 출력을 생성하거나 입력을 예상 하는 경우 두 번째 함정 (영원히 매달림)을 피 하려면 입력 및 출력 스트림을 처리해야합니다.

명령이 실행 가능한 프로그램이라고 가정

Windows 운영 체제에서 많은 새로운 프로그래머는 우연히 Runtime.exec()같은 비 실행 명령을 위해 사용하려고 할 때 dircopy. 그 후, 그들은 Runtime.exec()세 번째 함정에 부딪 힙니다. Listing 4.4는 정확히 다음을 보여줍니다.

목록 4.4 BadExecWinDir.java

import java.util. *; import java.io. *; public class BadExecWinDir {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); 프로세스 proc = rt.exec ( "dir"); InputStream stdin = proc.getInputStream (); InputStreamReader isr = new InputStreamReader (stdin); BufferedReader br = 새로운 BufferedReader (isr); 문자열 줄 = null; System.out.println ( ""); while ((line = br.readLine ())! = null) System.out.println (line); System.out.println ( ""); int exitVal = proc.waitFor (); System.out.println ( "Process exitValue :"+ exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

일련의 BadExecWinDir생산물 :

E:\classes\com\javaworld\jpitfalls\article2>java BadExecWinDir java.io.IOException: CreateProcess: dir error=2 at java.lang.Win32Process.create(Native Method) at java.lang.Win32Process.(Unknown Source) at java.lang.Runtime.execInternal(Native Method) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at BadExecWinDir.main(BadExecWinDir.java:12) 

앞에서 언급했듯이 오류 값은 2"파일을 찾을 수 없음" 을 의미하며,이 경우 이름 dir.exe이 지정된 실행 파일을 찾을 수 없음을 의미합니다 . 이는 디렉터리 명령이 별도의 실행 파일이 아니라 Windows 명령 인터프리터의 일부이기 때문입니다. Windows 명령 인터프리터를 실행 하려면 사용하는 Windows 운영 체제에 따라 command.com또는 을 실행하십시오 cmd.exe. 목록 4.5는 Windows 명령 인터프리터의 복사본을 실행 한 다음 사용자가 제공 한 명령 (예 :)을 실행합니다 dir.

목록 4.5 GoodWindowsExec.java