ProcessBuilder dari chapter sebelumnya sudah mengatur konfigurasi peluncuran; sisa pekerjaannya justru yang paling sering menjegal pemrogram: membaca keluaran tanpa jalan buntu, mengumpan masukan, menunggu dengan batas waktu, dan menghentikan proses yang membandel. Semua itu terjadi pada objek Process yang dikembalikan start() — representasi satu proses sistem operasi yang berjalan paralel dengan JVM. Tiga aliran pipa menghubungkan kedua proses, dan cara kita mengelolanya menentukan apakah program berjalan mulus atau berhenti diam tanpa pesan. Chapter ini membedah keempat persoalan itu dengan contoh yang sepenuhnya bisa dijalankan ulang.
Membaca Keluaran Secara Utuh #
Sisi pipa sering membingungkan arahnya: anak.getInputStream() adalah keluaran anak (stdout-nya dibaca parent), anak.getOutputStream() adalah masukan anak, dan anak.getErrorStream() membawa stderr. Kapasitas pipa sistem operasi terbatas — sekitar 64 KB di Linux. Anak yang menulis melewati kapasitas itu akan terblokir sampai parent membaca, jadi urutan yang benar adalah baca seluruh keluaran lebih dulu, baru waitFor(). Program berikut memicu 30.000 baris (±168 KB) dari seq, membacanya sampai habis, lalu menunggu:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class BacaUrunya {
public static void main(String[] args) throws Exception {
// seq menghasilkan 30000 baris (±168 KB), jauh melebihi kapasitas pipa.
Process anak = new ProcessBuilder("/bin/sh", "-c", "seq 1 30000").start();
BufferedReader pembaca = new BufferedReader(
new InputStreamReader(anak.getInputStream(), StandardCharsets.UTF_8));
long total = 0;
int baris = 0;
String awal = null, akhir = null;
String barisSekarang;
while ((barisSekarang = pembaca.readLine()) != null) {
total += Long.parseLong(barisSekarang);
if (awal == null) awal = barisSekarang;
akhir = barisSekarang;
baris++;
}
int keluar = anak.waitFor(); // aman: seluruh output sudah terbaca
System.out.println("jumlah baris : " + baris);
System.out.println("awal / akhir : " + awal + " / " + akhir);
System.out.println("total : " + total);
System.out.println("exit code : " + keluar);
}
}Output yang Dihasilkan:
jumlah baris : 30000
awal / akhir : 1 / 30000
total : 450015000
exit code : 0Bedah baris-per-barisnya:
- Baris
new ProcessBuilder("/bin/sh", "-c", "seq 1 30000").start():seqdijalankan lewatsh -cdengan argumen tetap; keluarannya mengalir ke pipa stdout yang menampung paling banyak 64 KB sebelum menahan penulis. - Baris
while ((barisSekarang = pembaca.readLine()) != null): loop membaca sampai EOF — tanda bahwa anak selesai menulis — sehingga penahanan pipa tidak pernah terjadi. - Baris
anak.waitFor()setelah loop: menunggu baru dilakukan setelah data habis; total450015000cocok dengan rumus30000 × 30001 / 2, membuktikan tidak ada baris yang hilang.
Mengumpan Masukan ke Proses #
Masukan ditulis ke anak.getOutputStream() dan satu detail wajib diingat: pipa harus ditutup agar anak menerima sinyal EOF. Tanpa penutupan itu, anak seperti sort akan menunggu masukan berikutnya selamanya. Program berikut mengirim daftar buah yang sama ke dua anak — tr yang mengkapitalkan huruf dan sort yang mengurutkan:
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
public class PipaTeks {
public static void main(String[] args) throws Exception {
List<String> nama = Arrays.asList("durian", "apel", "jeruk", "apel");
// 1) Umpan tetap masuk ke tr: huruf kecil menjadi besar.
Process besar = new ProcessBuilder("/usr/bin/tr", "a-z", "A-Z").start();
try (OutputStream masuk = besar.getOutputStream()) {
masuk.write(String.join("\n", nama).getBytes(StandardCharsets.UTF_8));
} // close() memberi sinyal EOF ke anak
String hasilBesar = new String(besar.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
besar.waitFor();
// 2) Umpan yang sama masuk ke sort: keluaran terurut.
Process urut = new ProcessBuilder("/usr/bin/sort").start();
try (OutputStream masuk = urut.getOutputStream()) {
masuk.write(String.join("\n", nama).getBytes(StandardCharsets.UTF_8));
}
String hasilUrut = new String(urut.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
urut.waitFor();
System.out.println("besar : " + hasilBesar.replace("\n", " ").trim());
System.out.println("urut : " + hasilUrut.replace("\n", " ").trim());
}
}Output yang Dihasilkan:
besar : DURIAN APEL JERUK APEL
urut : apel apel durian jerukDua baris try (OutputStream masuk = ...) menutup pipa masukan otomatis setelah blok berakhir — inilah sinyal EOF yang membuat tr dan sort selesai lalu menutup keluarannya masing-masing.
Exit Code, Batas Waktu, dan Penghentian #
waitFor() tanpa argumen mengembalikan exit code dan menunggu tanpa batas — bentuk yang paling jarang kita inginkan. Versi berbatas waktu, waitFor(200, TimeUnit.MILLISECONDS), mengembalikan false bila anak belum selesai, dan keputusan selanjutnya ada di tangan kita: destroy() meminta anak berhenti teratur (sinyal terminate), destroyForcibly() memaksanya seketika (sinyal kill). Konvensi exit code pada Unix dirangkum tabel berikut:
| Nilai exit | Arti |
|---|---|
| 0 | sukses |
| 1-125 | kode milik program itu sendiri |
| 128 + n | proses dihentikan sinyal ke-n |
| 143 | 128 + 15 — SIGTERM, hasil destroy() |
| 137 | 128 + 9 — SIGKILL, hasil destroyForcibly() |
Program berikut melatih ketiganya dengan dua anak sleep 60 yang sengaja dihentikan lebih awal:
import java.util.concurrent.TimeUnit;
public class HentikanProses {
public static void main(String[] args) throws Exception {
// Proses berumur panjang: sleep selama 60 detik.
Process lambat = new ProcessBuilder("/bin/sleep", "60").start();
boolean selesaiDalam = lambat.waitFor(200, TimeUnit.MILLISECONDS);
System.out.println("selesai dalam 200 ms : " + selesaiDalam);
// destroy() mengirim sinyal terminate; anak berhenti teratur.
lambat.destroy();
lambat.waitFor();
System.out.println("exit setelah destroy : " + lambat.exitValue());
// destroyForcibly() mengirim sinyal kill; tidak ada tahap bersih-bersih.
Process membandel = new ProcessBuilder("/bin/sleep", "60").start();
membandel.waitFor(200, TimeUnit.MILLISECONDS);
membandel.destroyForcibly();
membandel.waitFor();
System.out.println("exit setelah forcibly : " + membandel.exitValue());
}
}Output yang Dihasilkan:
selesai dalam 200 ms : false
exit setelah destroy : 143
exit setelah forcibly : 137Urutan pola di atas adalah resep lengkap pengawasan proses: tunggu berbatas waktu, periksa hasilnya, lalu tingkatkan tekanan dari destroy ke destroyForcibly hanya bila anak mengabaikan permintaan pertama.
Tiga aliran pipa yang kita kelola di sini sebenarnya adalah pewarisan langsung dari tiga aliran standar Unix — stdin, stdout, stderr. Chapter berikutnya menyoroti ketiganya dari sisi sebaliknya: bagaimana program Java kita sendiri membaca System.in dan memilahkan System.out dari System.err.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BacaUrunya.java
Unduh berkasimport java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class BacaUrunya {
public static void main(String[] args) throws Exception {
// seq menghasilkan 30000 baris (±168 KB), jauh melebihi kapasitas pipa.
Process anak = new ProcessBuilder("/bin/sh", "-c", "seq 1 30000").start();
BufferedReader pembaca = new BufferedReader(
new InputStreamReader(anak.getInputStream(), StandardCharsets.UTF_8));
long total = 0;
int baris = 0;
String awal = null, akhir = null;
String barisSekarang;
while ((barisSekarang = pembaca.readLine()) != null) {
total += Long.parseLong(barisSekarang);
if (awal == null) awal = barisSekarang;
akhir = barisSekarang;
baris++;
}
int keluar = anak.waitFor(); // aman: seluruh output sudah terbaca
System.out.println("jumlah baris : " + baris);
System.out.println("awal / akhir : " + awal + " / " + akhir);
System.out.println("total : " + total);
System.out.println("exit code : " + keluar);
}
}
HentikanProses.java
Unduh berkasimport java.util.concurrent.TimeUnit;
public class HentikanProses {
public static void main(String[] args) throws Exception {
// Proses berumur panjang: sleep selama 60 detik.
Process lambat = new ProcessBuilder("/bin/sleep", "60").start();
boolean selesaiDalam = lambat.waitFor(200, TimeUnit.MILLISECONDS);
System.out.println("selesai dalam 200 ms : " + selesaiDalam);
// destroy() mengirim sinyal terminate; anak berhenti teratur.
lambat.destroy();
lambat.waitFor();
System.out.println("exit setelah destroy : " + lambat.exitValue());
// destroyForcibly() mengirim sinyal kill; tidak ada tahap bersih-bersih.
Process membandel = new ProcessBuilder("/bin/sleep", "60").start();
membandel.waitFor(200, TimeUnit.MILLISECONDS);
membandel.destroyForcibly();
membandel.waitFor();
System.out.println("exit setelah forcibly : " + membandel.exitValue());
}
}
PipaTeks.java
Unduh berkasimport java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
public class PipaTeks {
public static void main(String[] args) throws Exception {
List<String> nama = Arrays.asList("durian", "apel", "jeruk", "apel");
// 1) Umpan tetap masuk ke tr: huruf kecil menjadi besar.
Process besar = new ProcessBuilder("/usr/bin/tr", "a-z", "A-Z").start();
try (OutputStream masuk = besar.getOutputStream()) {
masuk.write(String.join("\n", nama).getBytes(StandardCharsets.UTF_8));
} // close() memberi sinyal EOF ke anak
String hasilBesar = new String(besar.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
besar.waitFor();
// 2) Umpan yang sama masuk ke sort: keluaran terurut.
Process urut = new ProcessBuilder("/usr/bin/sort").start();
try (OutputStream masuk = urut.getOutputStream()) {
masuk.write(String.join("\n", nama).getBytes(StandardCharsets.UTF_8));
}
String hasilUrut = new String(urut.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
urut.waitFor();
System.out.println("besar : " + hasilBesar.replace("\n", " ").trim());
System.out.println("urut : " + hasilUrut.replace("\n", " ").trim());
}
}