JavaRush /Курси /JAVA 25 SELF /Розбір типових помилок під час роботи з процесами

Розбір типових помилок під час роботи з процесами

JAVA 25 SELF
Рівень 61 , Лекція 4
Відкрита

1. Взаємне блокування під час читання/запису потоків

Одна з найнеприємніших помилок — коли процес зависає й не завершується, хоча здається, що все зроблено правильно. Часта причина — переповнення буфера стандартного виведення або потоку помилок зовнішнього процесу. Якщо не читати обидва потоки (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/macOS, але не в 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. Взаємне блокування під час інтерактивної взаємодії

Під час інтерактивного обміну легко потрапити в ситуацію взаємного блокування: Java чекає відповіді, а зовнішній процес — ваших даних. У підсумку обидва «мовчать». Або ви надіслали повідомлення, але не читаєте відповідь — у певний момент буфер зовнішньої програми переповнюється, і вона припиняє запис.

Щоб цього уникнути, розділяйте обов’язки: один потік відповідає за читання, інший — за запис. Тоді взаємодія відбувається паралельно, і ніхто нікого не блокує. Також не залишайте відкриті потоки після завершення — закрийте їх, щоб система не тримала зайві ресурси.

1
Опитування
Робота з процесами, рівень 61, лекція 4
Недоступний
Робота з процесами
Робота з процесами
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ