1. Deadlock при чтении/записи потоков
Одна из самых неприятных ошибок — когда процесс зависает и не завершается, хотя кажется, что всё сделано правильно. Частая причина — переполнение буфера вывода или ошибок внешнего процесса. Если не читать оба потока (stdout и stderr), процесс может «заблокироваться» при попытке что-то вывести, потому что никто не забирает данные из его буфера.
Пример беды
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Читаем только stdout, а stderr игнорируем!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
Если команда пишет что-то в stderr (например, java -version почти всегда пишет туда!), этот поток переполняется, и процесс зависает.
Как правильно
Читайте оба потока параллельно (через отдельные потоки или ExecutorService):
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Читаем stdout
Thread stdoutThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[stdout] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// Читаем stderr
Thread stderrThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println("[stderr] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
stdoutThread.start();
stderrThread.start();
process.waitFor();
stdoutThread.join();
stderrThread.join();
Вывод:
Если не читать stderr — процесс может зависнуть.
Если не читать stdout — тоже может зависнуть.
Читай оба потока — и будет тебе счастье!
2. Проблемы с кодировкой
Внешние процессы могут использовать другую кодировку для вывода текста, чем ваша Java‑программа по умолчанию. Если не указать правильную кодировку, получите «кракозябры» вместо текста (особенно заметно с кириллицей).
Пример ошибки
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Этот код использует системную кодировку по умолчанию. Но если внешний процесс пишет, например, в UTF-8, а у вас Windows-1251, кириллица превратится в абракадабру.
Как правильно
Передавайте явно нужную кодировку, если знаете её:
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);
Если не уверены — изучите документацию к программе или попробуйте несколько вариантов.
Лайфхак: на Windows консольные утилиты часто используют CP866 или Windows-1251, а на Linux — UTF-8.
3. Платформенные различия
Команды, работающие на одной ОС, могут отсутствовать в другой. Например, ls есть на Linux/Mac, но не на Windows (там — dir). Отличаются синтаксис команд, разделители путей и кавычки.
Пример ошибки
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // На Windows: "ls" не найдена!
Как правильно
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder;
if (os.contains("win")) {
builder = new ProcessBuilder("cmd", "/c", "dir");
} else {
builder = new ProcessBuilder("ls", "-l");
}
Путь к файлам: используйте File.separator вместо «/» или «\», чтобы не попасть впросак с путями.
4. Проблемы с правами доступа
Некоторые команды требуют прав администратора (удаление системных файлов, изменение сетевых настроек и т. п.). При недостатке прав команда завершится с ошибкой или вовсе не запустится.
Пример
ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... ждём ошибку Permission denied
Как правильно
- Проверяйте, нужны ли повышенные права для вашей команды.
- Обрабатывайте код возврата процесса через process.exitValue().
- Читайте stderr — там чаще всего есть причина ошибки (например, «Permission denied»).
5. Утечки ресурсов
Если не закрывать потоки процесса (InputStream, OutputStream, ErrorStream), они могут «висеть», занимать ресурсы и даже блокировать завершение. Аналогично, если не завершить сам процесс, он может стать «зомби» в системе.
Пример ошибки
Process process = builder.start();
// ... работаем, но не закрываем потоки!
Как правильно
Используйте try-with-resources для потоков:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
// Читаем вывод
}
После завершения работы с процессом корректно его останавливайте:
process.destroy(); // Завершить процесс (если он ещё жив)
Внимание: если не закрыть потоки — возможны утечки памяти, зависания и системные проблемы (особенно при массовом запуске процессов).
6. Deadlock при интерактивном взаимодействии
При интерактивном обмене легко попасть в ситуацию взаимной блокировки: Java ждёт ответ, а внешний процесс — ваших данных. В итоге оба «молчат». Или вы отправили сообщение, но не читаете ответ — в какой-то момент у внешней программы переполняется буфер, и она останавливает запись.
Чтобы этого избежать, разделяйте обязанности: один поток отвечает за чтение, другой — за запись. Тогда взаимодействие идёт параллельно, и никто никого не держит. Также не оставляйте открытые потоки после завершения — закройте их, чтобы система не держала лишние ресурсы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ