1. OS에서 프로세스란 무엇이며, 왜 Java에서 실행하나요
프로세스: JVM에서 bash로, 그리고 반대로
컴퓨터를 켜고 브라우저, 메신저, 게임을 실행하면 — 이 모든 것이 각각의 프로세스입니다. 운영체제는 각 프로그램을 위해 별도의 “미니 세계”를 만들어 메모리, CPU 시간, 파일과 네트워크 접근을 할당합니다. 이렇게 프로그램들은 나란히 실행되지만 서로를 방해하지 않습니다.
Java 프로그램도 예외가 아닙니다. java MyApp을 실행하면, 시스템은 필요한 자원이 갖춰진 별도 프로세스를 하나 만듭니다. 그 안에서 여러분의 프로그램이 동작하며 계산하고, 그리며, 파일을 읽는 등 해야 할 일을 수행합니다.
하지만 가끔은 Java만으로 부족할 때가 있습니다. 예를 들어 파일을 압축하려고 압축 프로그램을 호출하거나, 동영상을 처리하려고 ffmpeg를 실행하거나, 설치된 Java 버전을 알아내고 싶을 수 있습니다. 이것이 바로 외부 프로세스 실행입니다: Java가 시스템에게 “이 유틸리티를 실행해줘”라고 요청하고, 그 결과를 받아오는 것이죠.
본질적으로 이는 프로그램을 더 유연하게 만드는 방법입니다. 서로 다른 도구를 결합하고, 반복 작업을 자동화하며, 기존 시스템 프로세스에 여러분의 로직을 연결할 수 있습니다. 때로는 코드로 바퀴를 다시 발명하기보다 외부 명령에 일부 작업을 맡기는 편이 더 쉽습니다.
JVM vs 외부 프로세스
JVM 프로세스 — Java 가상 머신에서 실행 중인 여러분의 프로그램입니다.
외부 프로세스 — 계산기, Python 스크립트, 명령 프롬프트, 심지어 또 다른 Java 인스턴스 같은 다른 프로그램입니다.
2. 클래스 ProcessBuilder
옛날에는 Java가 Runtime.getRuntime().exec() 메서드로 프로세스를 실행했습니다. 이는 썩 편리하거나 안전한 방식이 아니었죠 — 현미경으로 못을 박는 격이었습니다. 그래서 Java 5부터 ProcessBuilder 클래스가 도입되어 외부 프로세스를 더 유연하고 명확하게 생성·설정·실행할 수 있게 되었습니다.
ProcessBuilder는 일종의 “생성기”로, 실행할 프로세스의 매개변수(명령, 인수, 작업 폴더, 환경 변수 등)를 미리 지정할 수 있습니다.
문법: 프로세스 생성
ProcessBuilder pb = new ProcessBuilder("명령", "인수1", "인수2", ...);
- 첫 번째 인수 — 명령 이름(예: "ls", "dir", "ping", "java").
- 나머지는 명령에 전달할 매개변수입니다.
예: ls (Linux/Mac) 또는 dir (Windows) 실행
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
참고로 Windows에서 dir, copy 같은 명령은 별도의 실행 파일이 아니라 명령 프롬프트(cmd.exe)에 “내장된” 명령입니다. 따라서 cmd.exe /c ...를 통해 실행해야 합니다.
예: 간단한 프로세스 실행
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. 프로세스 환경 설정
인수 전달. 명령의 인수는 각기 별도의 문자열로 전달합니다:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
작업 디렉터리 지정. 기본적으로 프로세스는 프로그램과 같은 폴더에서 시작됩니다. 다른 디렉터리를 명시할 수도 있습니다:
pb.directory(new java.io.File("/tmp")); // Linux/Mac용
pb.directory(new java.io.File("C:\\Temp")); // Windows용
환경 변수 변경. 각 프로세스에는 자체 환경 변수 세트가 있습니다. 이를 추가하거나 변경할 수 있습니다:
pb.environment().put("MY_VAR", "HelloFromJava");
외부 프로세스가 특정 환경 변수를 기대하는 경우 유용합니다.
4. 프로세스 실행
메서드 start(). 모든 설정을 마쳤다면 이제 프로세스를 시작합니다:
Process process = pb.start();
start() 메서드는 Process 객체를 반환하며, 이를 통해 실행된 프로그램을 제어할 수 있습니다. 즉, 그 출력(표준 출력)을 읽고, 입력을 제공하고, 종료를 기다리는 등의 작업이 가능합니다.
예외 처리. start()는 명령을 찾지 못했거나 권한이 없거나 기타 실행 오류가 발생한 경우 IOException을 던질 수 있습니다.
예:
try {
Process process = pb.start();
// 프로세스와 상호작용...
} catch (IOException e) {
System.out.println("프로세스 시작 오류: " + e.getMessage());
}
5. 실습: 간단한 명령 실행
예제 1: 폴더의 파일 목록 출력
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// OS에 따라 명령 결정
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// 프로세스 출력(stdout) 읽기
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 프로세스가 종료될 때까지 대기
int exitCode = process.waitFor();
System.out.println("프로세스가 다음 코드로 종료됨: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("오류: " + e.getMessage());
}
}
}
여기서 무엇을 했나요?
- OS를 확인해 올바른 명령을 선택합니다.
- ProcessBuilder를 필요한 명령으로 생성합니다.
- start()로 프로세스를 시작합니다.
- 프로세스의 stdout에서 줄을 읽어 화면에 출력합니다.
- 프로세스 종료를 기다립니다(waitFor()).
- 반환 코드를 출력합니다(0은 성공, 그 외는 오류).
예제 2: java -version 실행
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version는 stderr로 출력하므로 getErrorStream()을 읽습니다
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
중요한 포인트: 일부 명령(예: java -version)은 표준 출력(stdout)이 아니라 오류 스트림(stderr)에 정보를 기록합니다. 따라서 process.getErrorStream()을 읽어야 할 때가 있습니다.
6. 크로스플랫폼: Windows와 Linux/Mac의 차이
- 명령과 그 매개변수가 다를 수 있습니다.
- 파일 경로 표기가 다릅니다 (C:\Temp vs /tmp).
- 일부 명령(예: ls, cat)은 유닉스 계열에만 있고, Windows에는 대체 명령(dir, type)이 있습니다.
- Windows에서는 내장 명령을 cmd.exe /c 명령을 통해서만 실행할 수 있습니다.
OS 확인 예:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
팁: 크로스플랫폼을 목표로 한다면, 대상 OS에서 항상 프로그램을 테스트하세요.
7. 표: ProcessBuilder의 주요 메서드와 기능
| 메서드/필드 | 용도 | 사용 예 |
|---|---|---|
|
명령과 인수로 프로세스 생성 | |
|
작업 디렉터리 지정 | |
|
환경 변수 가져오기/변경 | |
|
프로세스 시작 | |
|
프로세스의 stdout 가져오기 | |
|
프로세스의 stderr 가져오기 | |
|
프로세스의 stdin 가져오기 | |
|
프로세스 종료 대기 | |
|
프로세스의 반환 코드 가져오기 | |
8. 외부 프로세스 실행 시 흔한 실수
오류 №1: 명령을 찾을 수 없음. 명령 이름을 잘못 입력했거나 시스템에 없으면 IOException이 발생합니다: Cannot run program .... 예: Windows에서 ls를 실행하려는 경우.
오류 №2: 인수 전달 방식이 잘못됨. 전체 명령을 하나의 문자열로 합치지 마세요! 올바른 예: new ProcessBuilder("ping", "google.com"). 잘못된 예: new ProcessBuilder("ping google.com").
오류 №3: OS 차이를 고려하지 않음. Linux에서 잘 동작하는 명령이 Windows에는 없을 수 있고, 그 반대도 마찬가지입니다. 항상 OS를 확인하고 명령을 조정하세요.
오류 №4: 프로세스 출력을 처리하지 않음. 프로세스 출력을 읽지 않으면 버퍼가 가득 차 “멈춰” 버릴 수 있습니다. 출력이 필요 없더라도 읽어서 무시하는 식으로 처리하세요.
오류 №5: 스트림을 닫지 않음. 사용이 끝난 후 프로세스의 스트림을 닫지 않으면 자원 누수가 발생할 수 있습니다.
오류 №6: 예외를 처리하지 않음. 외부 프로세스 실행은 실패 가능성이 있습니다. 항상 try-catch를 사용하고 사용자에게 오류를 알려 주세요.
GO TO FULL VERSION