Comment puis-je terminer une arborescence de processus à partir de Java?

J’utilise la commande Runtime.getRuntime (). exec () en Java pour démarrer un fichier de traitement par lots qui lance à son tour un autre processus pour la plate-forme Windows.

javaw.exe(Process1) |___xyz.bat(Process2) |___javaw.exe(Process3) 

Runtime.getRuntime (). Exec () renvoie un object Process qui a une méthode destroy, mais quand j’utilise destroy (), il ne tue que le fichier xyz.bat et laisse le sous-processus du sous-processus.

Existe-t-il un moyen propre en Java de détruire l’arborescence des processus en commençant par le traitement par lots en tant que root?

* Je ne peux pas utiliser de bibliothèques personnalisées \ Débarrassez-vous du fichier de commandes pour contourner le problème

Cela n’est pas possible avec l’API Java standard (voir edit à la fin du message pour une mise à jour qui change cela). Vous aurez besoin d’un code natif d’une certaine variété. En utilisant JNA, j’ai utilisé du code qui ressemble à ceci:

 public class Win32Process { WinNT.HANDLE handle; int pid; Win32Process (int pid) throws IOException { handle = Kernel32.INSTANCE.OpenProcess ( 0x0400| /* PROCESS_QUERY_INFORMATION */ 0x0800| /* PROCESS_SUSPEND_RESUME */ 0x0001| /* PROCESS_TERMINATE */ 0x00100000 /* SYNCHRONIZE */, false, pid); if (handle == null) throw new IOException ("OpenProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode (Kernel32.INSTANCE.GetLastError ())); this.pid = pid; } @Override protected void finalize () throws Throwable { Kernel32.INSTANCE.CloseHandle (handle); } public void terminate () { Kernel32.INSTANCE.TerminateProcess (handle, 0); } public List getChildren () throws IOException { ArrayList result = new ArrayList (); WinNT.HANDLE hSnap = KernelExtra.INSTANCE.CreateToolhelp32Snapshot (KernelExtra.TH32CS_SNAPPROCESS, new DWORD(0)); KernelExtra.PROCESSENTRY32.ByReference ent = new KernelExtra.PROCESSENTRY32.ByReference (); if (!KernelExtra.INSTANCE.Process32First (hSnap, ent)) return result; do { if (ent.th32ParentProcessID.intValue () == pid) result.add (new Win32Process (ent.th32ProcessID.intValue ())); } while (KernelExtra.INSTANCE.Process32Next (hSnap, ent)); Kernel32.INSTANCE.CloseHandle (hSnap); return result; } 

}

Ce code utilise les déclarations JNA suivantes qui ne sont pas incluses dans la bibliothèque JNA standard:

 public interface KernelExtra extends StdCallLibrary { /** * Includes all heaps of the process specified in th32ProcessID in the snapshot. To enumerate the heaps, see * Heap32ListFirst. */ WinDef.DWORD TH32CS_SNAPHEAPLIST = new WinDef.DWORD(0x00000001); /** * Includes all processes in the system in the snapshot. To enumerate the processes, see Process32First. */ WinDef.DWORD TH32CS_SNAPPROCESS = new WinDef.DWORD(0x00000002); /** * Includes all threads in the system in the snapshot. To enumerate the threads, see Thread32First. */ WinDef.DWORD TH32CS_SNAPTHREAD = new WinDef.DWORD(0x00000004); /** * Includes all modules of the process specified in th32ProcessID in the snapshot. To enumerate the modules, see * Module32First. If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds. */ WinDef.DWORD TH32CS_SNAPMODULE = new WinDef.DWORD(0x00000008); /** * Includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit * process. This flag can be combined with TH32CS_SNAPMODULE or TH32CS_SNAPALL. If the function fails with * ERROR_BAD_LENGTH, retry the function until it succeeds. */ WinDef.DWORD TH32CS_SNAPMODULE32 = new WinDef.DWORD(0x00000010); /** * Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID. */ WinDef.DWORD TH32CS_SNAPALL = new WinDef.DWORD((TH32CS_SNAPHEAPLIST.intValue() | TH32CS_SNAPPROCESS.intValue() | TH32CS_SNAPTHREAD.intValue() | TH32CS_SNAPMODULE.intValue())); /** * Indicates that the snapshot handle is to be inheritable. */ WinDef.DWORD TH32CS_INHERIT = new WinDef.DWORD(0x80000000); /** * Describes an entry from a list of the processes residing in the system address space when a snapshot was taken. */ public static class PROCESSENTRY32 extends Structure { public static class ByReference extends PROCESSENTRY32 implements Structure.ByReference { public ByReference() { } public ByReference(Pointer memory) { super(memory); } } public PROCESSENTRY32() { dwSize = new WinDef.DWORD(size()); } public PROCESSENTRY32(Pointer memory) { useMemory(memory); read(); } /** * The size of the structure, in bytes. Before calling the Process32First function, set this member to * sizeof(PROCESSENTRY32). If you do not initialize dwSize, Process32First fails. */ public WinDef.DWORD dwSize; /** * This member is no longer used and is always set to zero. */ public WinDef.DWORD cntUsage; /** * The process identifier. */ public WinDef.DWORD th32ProcessID; /** * This member is no longer used and is always set to zero. */ public BaseTSD.ULONG_PTR th32DefaultHeapID; /** * This member is no longer used and is always set to zero. */ public WinDef.DWORD th32ModuleID; /** * The number of execution threads started by the process. */ public WinDef.DWORD cntThreads; /** * The identifier of the process that created this process (its parent process). */ public WinDef.DWORD th32ParentProcessID; /** * The base priority of any threads created by this process. */ public WinDef.LONG pcPriClassBase; /** * This member is no longer used, and is always set to zero. */ public WinDef.DWORD dwFlags; /** * The name of the executable file for the process. To resortingeve the full path to the executable file, call the * Module32First function and check the szExePath member of the MODULEENTRY32 structure that is returned. * However, if the calling process is a 32-bit process, you must call the QueryFullProcessImageName function to * resortingeve the full path of the executable file for a 64-bit process. */ public char[] szExeFile = new char[WinDef.MAX_PATH]; } // the following methods are in kernel32.dll, but not declared there in the current version of Kernel32: /** * Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes. * * @param dwFlags * The portions of the system to be included in the snapshot. * * @param th32ProcessID * The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate * the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, * TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are * included in the snapshot. * * If the specified process is the Idle process or one of the CSRSS processes, this function fails and the last * error code is ERROR_ACCESS_DENIED because their access ressortingctions prevent user-level code from opening them. * * If the specified process is a 64-bit process and the caller is a 32-bit process, this function fails and the * last error code is ERROR_PARTIAL_COPY (299). * * @return * If the function succeeds, it returns an open handle to the specified snapshot. * * If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. * Possible error codes include ERROR_BAD_LENGTH. */ public WinNT.HANDLE CreateToolhelp32Snapshot(WinDef.DWORD dwFlags, WinDef.DWORD th32ProcessID); /** * Resortingeves information about the first process encountered in a system snapshot. * * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. * @param lppe A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the * executable file, the process identifier, and the process identifier of the parent process. * @return * Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot * does not contain process information. */ public boolean Process32First(WinNT.HANDLE hSnapshot, KernelExtra.PROCESSENTRY32.ByReference lppe); /** * Resortingeves information about the next process recorded in a system snapshot. * * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. * @param lppe A pointer to a PROCESSENTRY32 structure. * @return * Returns TRUE if the next entry of the process list has been copied to the buffer or FALSE otherwise. The * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot * does not contain process information. */ public boolean Process32Next(WinNT.HANDLE hSnapshot, KernelExtra.PROCESSENTRY32.ByReference lppe); } 

Vous pouvez ensuite utiliser la méthode ‘getChildren ()’ pour obtenir une liste d’enfants, mettre fin au parent, puis mettre fin de manière récursive aux enfants.

Je pense que vous pouvez extraire le PID d’un java.lang.Process en utilisant la reflection (je ne l’ai pas fait, cependant, je suis passé à la création des processus moi-même en utilisant l’API Win32 pour avoir plus de contrôle).

Donc, en le réunissant, vous avez besoin de quelque chose comme:

 int pid = (some code to extract PID from the process you want to kill); Win32Process process = new Win32Process(pid); kill(process); public void kill(Win32Process target) throws IOException { List children = target.getChildren (); target.terminateProcess (); for (Win32Process child : children) kill(child); } 

modifier

Il s’avère que ce défaut particulier de l’API Java est en train d’être corrigé dans Java 9. Voir l’aperçu de la documentation de Java 9 ici (si la page correcte ne se charge pas, vous devez regarder l’interface java.lang.ProcessHandle ) . Pour l’exigence de la question ci-dessus, le code ressemblerait maintenant à ceci:

 Process child = ...; kill (child.toHandle()); public void kill (ProcessHandle handle) { handle.descendants().forEach((child) -> kill(child)); handle.destroy(); } 

(Notez que ceci n’a pas été testé – je ne suis pas encore passé à Java 9, mais je suis en train de lire à ce sujet)

Une autre solution, si vous contrôlez le processus enfant ainsi que le fichier de commandes, serait que le processus enfant crée un thread, ouvre un ServerSocket, écoute une connexion et appelle System.exit () s’il reçoit un mot de passe correct sur celui-ci.

Il peut y avoir des complications si vous avez besoin de plusieurs instances simultanées; à ce stade, vous auriez besoin d’un moyen de leur atsortingbuer des numéros de port.

Vous ne pouvez pas tuer une arborescence de processus pour Windows en utilisant JDK. Vous devez vous fier à WinAPI. Vous devrez recourir à des commandes natives ou à des bibliothèques JNI, toutes dépendantes de la plate-forme et plus complexes qu’une solution Java pure.

Un exemple de lien Exemple JNI

Voici une autre option. Utilisez ce script powershell pour exécuter votre script bat. Lorsque vous voulez tuer l’arborescence, arrêtez le processus de votre script powershell et il exécutera automatiquement taskkill sur son sous-processus. Je l’appelle taskkill deux fois parce que dans certains cas, il ne faut pas le premier essai.

 Param( [ssortingng]$path ) $p = [Diagnostics.Process]::Start("$path").Id try { while($true) { sleep 100000 } } finally { taskkill /pid $p taskkill /pid $p } 

Avec java 9, la destruction du processus principal tue l’arbre de processus entier. Vous pourriez faire quelque chose comme ça:

 Process ptree = Runtime.getRuntime().exec("cmd.exe","/c","xyz.bat"); // wait logic ptree.destroy(); 

S’il vous plaît jeter un oeil à ce blog et consultez l’exemple Deal with Process Trees .