#java #jvm #процесс #multiprocessing
Если создать процесс
ProcessBuilder builder = new ProcessBuilder("command");
final Process process = builder.start();
то его можно будет убить с помощью: process.destroy();
Но что, если этот command наплодит кучу других процессов? process.destroy(); уже
будет не в силах убить их. И они останутся в памяти.
При этом, если из командной строки сделать Ctrl+C, то дочерние процессы тоже завершатся.
В сети много вопросов на эту тему (большинство из них старые) и почти все ответы
говорят о том, что это невозможно сделать средствами JVM - нужно обращаться к ОС. Решил
задать вопрос, в надежде на то, что это всё же стало возможным с появлением Java 8.
Хочется получить решение, не зависимое от ОС.
Возможно ли на сегодняшний день средствами Java убить дочерние процессы процесса,
созданного с помощью ProcessBuilder, не манипулируя напрямую с командами ОС? Если возможно,
то как это реализовать? Если нет, то ожидается ли такая возможность в Java 9?
UPDATE
Хочу ещё раз выделить то, что я не ищу решения, зависящие от ОС. По этой ссылке можно
прочитать о том, как искать PID процесса в Unix и Windows. Потом можно будет обратиться
к терминалу с соответствующей командой для "убийства" процесса.
Это не является темой данного вопроса.
Ответы
Ответ 1
В версиях Java до 8й включительно инструментарий для работы с процессами был довольно скудным. Но если ознакомиться, с JEP 102: Process API Updates (который реализуется в рамках Java 9), то мы увидим, что Brian Goetz говорит нам о следующих вещах: Возможность получить pid процесса JVM и pid-ы процессов, запущенных средствами API. Возможность получить список запущенных в системе процессов, включая их pid, состояние, наименование и, возможно, потребление ресурсов. Возможность взаимодействовать с деревьями процессов, а именно прекращать работу целого дерева. Возможность взаимодействовать с сотнями дочерних процессов, возможно, мультиплексируя потоки вывода и ошибок, чтобы избежать создания отдельной нити (thread) на каждый процесс. Все эти радости уже можно потрогать в 9ке: см. интерфейс ProcessHandle: long getPid() static StreamallProcesses() и ProcessHandle.Info info() Stream descendants() Ответ 2
Если речь идет о "популярных" ОС для Java (Windows & unix-like), то на первой можно запустить программу taskkill/PID, а на вторых - kill-9 . На юниксе, кроме того, можно отправить сигнал группе процессов (process group id равен - процесса-родоначальника группы. Или воспользоваться библиотекой, дергающей нативные методы, типа Posix for Java (впрочем, для метода kill достаточно простого JNI вызова. Но средствами именно Java, да без сторонних библиотек, насколько я знаю, нет. Правда, говорят, что у Process так просто pid не получишь, но вот можно через Reflection достать приватное поле UnixProcess'а pid.. :) В 9-ой версии OpenJDK Process имеет метод getPid(), так что, теперь вышла нам всем поблажка в плане получения pid. Однако, никаких переносимых методов для управления группой процессов пока не просматривается. Ответ 3
Кроссплатформенного решения нету. Придётся писать для каждой ОС самому. Не так всё просто, по-этому вряд ли кто-то даст законченное решение. Мы можем только дать какие-то подсказки, пути к решению задачи. Unix Сначала надо получить PID нашего процесса. /** * Получить строку - pid программы - Java VM */ public static String getPid() throws IOException,InterruptedException { Vectorcommands=new Vector (); commands.add("/bin/bash"); commands.add("-c"); commands.add("echo $PPID"); ProcessBuilder pb=new ProcessBuilder(commands); Process pr=pb.start(); pr.waitFor(); if (pr.exitValue()==0) { BufferedReader outReader=new BufferedReader(new InputStreamReader(pr.getInputStream())); return outReader.readLine().trim(); } else { System.out.println("Error while getting PID"); return ""; } } С другой стороны java.lang.Process - абстрактный класс, конкретная реализация зависит от ОС. На Linux это java.lang.UnixProcess, у которой есть приватное поле pid. Используя рефлексию можно запросто получить поле: public static long getPidOfProcess(Process p) { long pid = -1; try { if (p.getClass().getName().equals("java.lang.UNIXProcess")) { Field f = p.getClass().getDeclaredField("pid"); f.setAccessible(true); pid = f.getLong(p); f.setAccessible(false); } } catch (Exception e) { pid = -1; } return pid; } Дальше необходимо получить список подпроцессов. Есть комманда pstree ${pid}. Можно выполнить команду из Java Process p = Runtime.getRuntime().exec("pstree ${pid}"); Затем спарсить список и вытащить PID'ы всех процессов. Потом используя Runtime.getRuntime().exec() убить все процессы по PID'ам, если надо. Windows Для получения PID'а можно сделать что-то такое. Тут сложнее. Я знаю, что можно получить список запущенных процессов. Process p = Runtime.getRuntime().exec("cmd /c tasklist"); StringWriter writer = new StringWriter(); IOUtils.copy(p.getInputStream(), writer); String theString = writer.toString(); Но вопрос как найти зависимости между процессами не ясен. Как минимум, можно выполнить tasklist и отфильтровать список по имени процесса. Процесс с большим PID вероятно и есть ваш процесс. Так вы получите PID вашего главного процесса. Можно попробовать запустить PowerShell скрипт для получения списка процессов (а потом отфильтровать по parent id). Такая команда в шеле: gwmi win32_process |select ProcessID,ParentProcessID,Name, @{l="Username";e={$_.getowner().user}}|where {$_.Username -ne "SYSTEM"} | where {$_.Username -ne "LOCAL SERVICE"} | where {$_.Username -ne "NETWORK SERVICE"} | where {$_.Username -ne $null} |Sort-Object ProcessID | ft -AutoSize Даст что-то такое: ProcessID ParentProcessID Name Username --------- --------------- ---- -------- 180 4536 chrome.exe Suvitruf 396 5272 slack.exe Suvitruf 1488 1040 taskeng.exe Suvitruf 1504 4008 BatteryLife.exe Suvitruf 1540 180 chrome.exe Suvitruf 1704 180 chrome.exe Suvitruf 2084 180 chrome.exe Suvitruf 2404 5272 slack.exe Suvitruf 2408 5272 slack.exe Suvitruf Вам надо понять как эту команду выполнить с использованием Runtime.getRuntime().exec(). После этого распарсить ответ и получить список PID'ов процессов. Для их убийства вызывать: String cmd = "taskkill /F /PID " + tokill; Runtime.getRuntime().exec(cmd);
Комментариев нет:
Отправить комментарий