关于windows:如何从Java终止进程树?

How do i terminate a process tree from Java?

我使用Java中的RunTime.GeTrutMeMe(.Excel)命令启动批处理文件,该批处理文件又启动了Windows平台的另一个进程。

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

runtime.getruntime().exec()返回一个具有destroy方法的进程对象,但当我使用destroy()时,它只会杀死xyz.bat并使批处理文件的子进程悬空。

Java中有一个干净的方法来破坏进程树,从批处理过程开始吗?

*我不能使用任何自定义库去掉批处理文件来绕过问题


这是不可能的使用标准Java API(见编辑在帖子的一个更新,改变这一点)。您需要一些不同种类的本地代码。使用JNA,我使用了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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<Win32Process> getChildren () throws IOException
    {
        ArrayList<Win32Process> result = new ArrayList<Win32Process> ();
        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;
    }

}

此代码使用标准JNA库中未包含的以下JNA声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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 retrieve 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
         * retrieve 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 restrictions 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);

    /**
     * Retrieves 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);

    /**
     * Retrieves 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);


}

然后,可以使用"get children()"方法获取子级列表,终止父级,然后递归终止子级。

我相信您可以使用反射来增加java.lang.process的pid(不过,我还没有这样做;我改用win32 api自己创建进程,这样我就可以更好地控制它)。

所以把它放在一起,你就需要这样的东西:

1
2
3
4
5
6
7
8
9
10
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<Win32Process> children = target.getChildren ();
   target.terminateProcess ();
   for (Win32Process child : children) kill(child);
}

编辑

原来Java API中的这个特殊缺陷在Java 9中被固定。请参阅Java 9文档的预览(如果正确的页面不加载,则需要查看EDCOX1×0接口)。对于上述问题的要求,代码现在看起来如下所示:

1
2
3
4
5
6
7
8
Process child = ...;
kill (child.toHandle());

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

(注意,这不是测试-我还没有切换到Java 9,但我积极阅读它)


如果控制子进程和批处理文件,另一种解决方案是让子进程创建一个线程,打开一个Serversocket,监听到它的连接,并在收到正确的密码时调用System.exit()。

如果您需要多个同时发生的实例,可能会有一些复杂的情况;此时,您需要某种方式来为它们分配端口号。


使用Java 9,杀死主进程杀死整个进程树。你可以这样做:

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

请看一下这个博客,并查看处理过程树示例。


这是另一个选择。使用此PowerShell脚本执行BAT脚本。当您想要终止树时,终止PowerShell脚本的进程,它将自动在其子进程上执行taskkill。我让它两次调用taskkill,因为在某些情况下,它不会进行第一次尝试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Param(
    [string]$path
)

$p = [Diagnostics.Process]::Start("$path").Id

try {
    while($true) {
        sleep 100000
    }
} finally {
    taskkill /pid $p
    taskkill /pid $p
}

不能使用JDK终止Windows的进程树。您需要依赖WinAPI。您必须求助于本地命令或JNI库,所有这些都是依赖于平台的,比纯Java解决方案更复杂。

链接JNI示例