Tips
做一个终身学习的人。
在本章中,主要介绍以下内容:
Process API是什么
如何创建本地进程
如何获取新进程的信息
如何获取当前进程的信息
如何获取所有系统进程的信息
如何设置创建,查询和管理本地进程的权限
一. Process API是什么
Process API 由接口和类组成,用来与本地进程一起工作,使用API,可以做以下事情:
从Java代码中创建新的本地进程
获取本地进程的进程句柄,无论它们是由Java代码还是通过其他方式创建
销毁运行进程
查询活动的进程及其属性
获取进程的子进程和父进程的列表
获取本地进程的进程ID(PID)
获取新创建的进程的输入,输出和错误流
等待进程终止
当进程终止时执行任务
Process API由java.lang包中的以下类和接口组成:
RuntimeProcessBuilderProcessBuilder.RedirectProcessProcessHandleProcessHandle.Info
自Java 1.0以来,支持使用本地进程。Process
类的实例表示由Java程序创建的本地进程。 通过调用Runtime
类的exec()
方法启动一个进程。
JDK 5.0添加了ProcessBuilder
类,JDK 7.0添加了ProcessBuilder.Redirect
的嵌套类。 ProcessBuilder
类的实例保存一个进程的一组属性。 调用其start()
方法启动本地进程并返回一个表示本地进程的Process
类的实例。 可以多次调用其start()
方法; 每次使用ProcessBuilder
实例中保存的属性启动一个新进程。 在Java 5.0中,ProcessBuilder
类接管Runtime.exec()
方法来启动新进程。
在Java 7和Java 8中的Process API中有一些改进,就是在Process
和ProcessBuilder
类中添加几个方法。
在Java 9之前,Process API仍然缺乏对使用本地进程的基本支持,例如获取进程的PID和所有者,进程的开始时间,进程使用了多少CPU时间,多少本地进程正在运行等。请注意,在Java 9之前,可以启动本地进程并使用其输入,输出和错误流。 但是,无法使用未启动的本地进程,无法查询进程的详细信息。 为了更紧密地处理本地进程,Java开发人员不得不使用Java Native Interface(JNI)来编写本地代码。 Java 9使这些非常需要的功能与本地进程配合使用。
Java 9向Process API添加了一个名为ProcessHandle
的接口。 ProcessHandle
接口的实例标识一个本地进程; 它允许查询进程状态并管理进程。
比较Process
类和ProcessHandle
接口。 Process
类的一个实例表示由当前Java程序启动的本地进程,而ProcessHandle
接口的实例表示本地进程,无论是由当前Java程序启动还是以其他方式启动。 在Java 9中,已经在Process
类中添加了几种方法,这些方法也可以在新的ProcessHandle
接口中使用。 Process
类包含一个返回ProcessHandle
的toHandle()
方法。
ProcessHandle.Info
接口的实例表示进程属性的快照。 请注意,进程由不同的操作系统不同地实现,因此它们的属性不同。 过程的状态可以随时更改,例如,当进程获得更多CPU时间时,进程使用的CPU时间增加。 要获取进程的最新信息,需要在需要时使用ProcessHandle
接口的info()
方法,这将返回一个新的ProcessHandle.Info
实例。
本章中的所有示例都在Windows 10中运行。当使用Windows 10或其他操作系统在机器上运行这些程序时,可能会得到不同的输出。
二. 当前进程
ProcessHandle
接口的current()
静态方法返回当前进程的句柄。 请注意,此方法返回的当前进程始终是正在执行代码的Java进程。
// Get the handle of the current processProcessHandle current = ProcessHandle.current();
获取当前进程的句柄后,可以使用ProcessHandle
接口的方法获取有关进程的详细信息。
Tips
你不能杀死当前进程。 尝试通过使用ProcessHandle
接口的destroy()
或destroyForcibly()
方法来杀死当前进程会导致IllegalStateException
异常。
三. 查询进程状态
可以使用ProcessHandle
接口中的方法来查询进程的状态。 下表列出了该接口常用的简单说明方法。 请注意,许多这些方法返回执行快照时进程状态的快照。 不过,由于进程是以异步方式创建,运行和销毁的,所以当稍后使用其属性时,所以无法保证进程仍然处于相同的状态。
方法 | 描述 |
---|---|
static Stream<ProcessHandle> allProcesses() | 返回操作系统中当前进程可见的所有进程的快照。 |
Stream<ProcessHandle> children() | 返回进程当前直接子进程的快照。 使用descendants() 方法获取所有级别的子级列表,例如子进程,孙子进程进程等。返回当前进程可见的操作系统中的所有进程的快照。 |
static ProcessHandle current() | 返回当前进程的ProcessHandle ,这是执行此方法调用的Java进程。 |
Stream<ProcessHandle> descendants() | 返回进程后代的快照。 与children() 方法进行比较,该方法仅返回进程的直接后代。 |
boolean destroy() | 请求进程被杀死。 如果成功请求终止进程,则返回true,否则返回false。 是否可以杀死进程取决于操作系统访问控制。 |
boolean destroyForcibly() | 要求进程被强行杀死。 如果成功请求终止进程,则返回true,否则返回false。 杀死进程会立即强制终止进程,而正常终止则允许进程彻底关闭。 是否可以杀死进程取决于操作系统访问控制。 |
long getPid() | 返回由操作系统分配的进程的本地进程ID(PID)。 注意,PID可以由操作系统重复使用,因此具有相同PID的两个处理句柄可能不一定代表相同的过程。 |
ProcessHandle.Info info() | 返回有关进程信息的快照。 |
boolean isAlive() | 如果此ProcessHandle 表示的进程尚未终止,则返回true,否则返回false。 请注意,在成功请求终止进程后,此方法可能会返回一段时间,因为进程将以异步方式终止。 |
static Optional<ProcessHandle> of(long pid) | 返回现有本地进程的Optional<ProcessHandle> 。 如果具有指定pid的进程不存在,则返回空的Optional 。 |
CompletableFuture <ProcessHandle> onExit() | 返回一个用于终止进程的CompletableFuture<ProcessHandle> 。 可以使用返回的对象来添加在进程终止时执行的任务。 在当前进程中调用此方法会引发IllegalStateException 异常。 |
Optional<ProcessHandle> parent() | 返回父进程的Optional<ProcessHandle> 。 |
boolean supportsNormalTermination() | 如果destroy() 的实现正常终止进程,则返回true。 |
下表列出ProcessHandle.Info
嵌套接口的方法和描述。 此接口的实例包含有关进程的快照信息。 可以使用ProcessHandle
接口或Process
类的info()
方法获取ProcessHandle.Info
。 接口中的所有方法都返回一个Optional
。
方法 | 描述 |
---|---|
Optional<String[]> arguments() | 返回进程的参数。 该过程可能会更改启动后传递给它的原始参数。 在这种情况下,此方法返回更改的参数。 |
Optional<String> command() | 返回进程的可执行路径名。 |
Optional<String> commandLine() | 它是一个进程的组合命令和参数的便捷的方法。如果command() 和arguments() 方法都没有返回空Optional , 它通过组合从command() 和arguments() 方法返回的值来返回进程的命令行。 |
Optional<Instant> startInstant() | 返回进程的开始时间。 如果操作系统没有返回开始时间,则返回一个空Optional 。 |
Optional<Duration> totalCpuDuration() | 返回进程使用的CPU时间。 请注意,进程可能运行很长时间,但可能使用很少的CPU时间。 |
Optional<String> user() | 返回进程的用户。 |
现在是时候看到ProcessHandle
和ProcessHandle.Info
接口的实际用法。 本章中的所有类都在com.jdojo.process.api模块中,其声明如下所示。
// module-info.javamodule com.jdojo.process.api { exports com.jdojo.process.api; }
接下来包含CurrentProcessInfo
类的代码。 它的printInfo()
方法将ProcessHandle
作为参数,并打印进程的详细信息。 我们还在其他示例中使用此方法打印进程的详细信息。main()
方法获取运行进程的当前进程的句柄,这是一个Java进程,并打印其详细信息。 你可能会得到不同的输出。 以下是当程序在Windows 10上运行时生成输出。
// CurrentProcessInfo.javapackage com.jdojo.process.api; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays;public class CurrentProcessInfo { public static void main(String[] args) { // Get the handle of the current process ProcessHandle current = ProcessHandle.current(); // Print the process details printInfo(current); } public static void printInfo(ProcessHandle handle) { // Get the process ID long pid = handle.getPid(); // Is the process still running boolean isAlive = handle.isAlive(); // Get other process info ProcessHandle.Info info = handle.info(); String command = info.command().orElse(""); String[] args = info.arguments() .orElse(new String[]{}); String commandLine = info.commandLine().orElse(""); ZonedDateTime startTime = info.startInstant() .orElse(Instant.now()) .atZone(ZoneId.systemDefault()); Duration duration = info.totalCpuDuration() .orElse(Duration.ZERO); String owner = info.user().orElse("Unknown"); long childrenCount = handle.children().count(); // Print the process details System.out.printf("PID: %d%n", pid); System.out.printf("IsAlive: %b%n", isAlive); System.out.printf("Command: %s%n", command); System.out.printf("Arguments: %s%n", Arrays.toString(args)); System.out.printf("CommandLine: %s%n", commandLine); System.out.printf("Start Time: %s%n", startTime); System.out.printf("CPU Time: %s%n", duration); System.out.printf("Owner: %s%n", owner); System.out.printf("Children Count: %d%n", childrenCount); } }
打印输出为:
PID: 8692IsAlive: trueCommand: C:\java9\bin\java.exeArguments: []CommandLine:Start Time: 2016-11-27T12:28:20.611-06:00[America/Chicago] CPU Time: PT0.296875SOwner: kishori\ksharan Children Count: 1
四. 比较进程
比较两个进程是否相等等或顺序是否相同是棘手的。 不能依赖PID来处理相同的进程。 操作系统在进程终止后重用PID。 可以与PID一起检查流程的开始时间;如果两者相同,则两个过程可能相同。 ProcessHandle
接口的默认实现的equals()
方法检查以下三个信息,以使两个进程相等:
对于这两个进程,
ProcessHandle
接口的实现类必须相同。进程必须具有相同的PID。
进程必须同一时间启动。
Tips
在ProcessHandle
接口中使用compareTo()
方法的默认实现对于排序来说并不是很有用。 它比较了两个进程的PID。
五. 创建进程
需要使用ProcessBuilder
类的实例来启动一个新进程。 该类包含几个方法来设置进程的属性。 调用start()
方法启动一个新进程。 start()
方法返回一个Process
对象,可以使用它来处理进程的输入,输出和错误流。 以下代码段创建一个ProcessBuilder
在Windows上启动JVM:
ProcessBuilder pb = new ProcessBuilder() .command("C:\\java9\\bin\\java.exe", "--module-path", "myModulePath", "--module", "myModule/className") .inheritIO();
有两种方法来设置这个新进程的命令和参数:
可以将它们传递给
ProcessBuilder
类的构造函数。可以使用
command()
方法。
没有参数的command()
方法返回在ProcessBuilder
中命令的设置的。 带有参数的其他版本 —— 一个带有一个String
的可变参数,一个带有List<String>
的版本,都用于设置命令和参数。 该方法的第一个参数是命令路径,其余的是命令的参数。
新进程有自己的输入,输出和错误流。 inheritIO()
方法将新进程的输入,输出和错误流设置为与当前进程相同。 ProcessBuilder
类中有几个redirectXxx()
方法可以为新进程定制标准I/O,例如将标准错误流设置为文件,因此所有错误都会记录到文件中。 配置进程的所有属性后,可以调用start()
来启动进程:
// Start a new process Process newProcess = pb.start();
可以多次调用ProcessBuilder
类的start()
方法来启动与之前保持的相同属性的多个进程。 这具有性能优势,可以创建一个ProcessBuilder
实例,并重复使用它来多次启动相同的进程。
可以使用Process
类的toHandle()
方法获取进程的进程句柄:
// Get the process handleProcessHandle handle = newProcess.toHandle();
可以使用进程句柄来销毁进程,等待进程完成,或查询进程的状态和属性,如其子进程,后代,父进程,使用的CPU时间等。有关进程的信息,对进程的控制取决于操作系统访问控制。
创建可以在所有操作系统上运行的进程都很棘手。 可以创建一个新进程启动新的JVM来运行一个类。
如下包含一个Job
类的代码。 它的main()
方法需要两个参数:睡眠间隔和睡眠持续时间(以秒为单位)。 如果没有参数传递,该方法将使用5秒和60秒作为默认值。 在第一部分中,该方法尝试提取第一个和第二个参数(如果指定)。 在第二部分中,它使用ProcessHandle.current()
方法获取当前进程执行此方法的进程句柄。 它读取当前进程的PID并打印包括PID,睡眠间隔和睡眠持续时间的消息。 最后,它开始一个for循环,并持续休眠睡眠间隔,直到达到睡眠持续时间。 在循环的每次迭代中,它打印一条消息。
// Job.javapackage com.jdojo.process.api;import java.io.IOException;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;import java.util.stream.Collectors;/** * An instance of this class is used as a job that sleeps at a * regular interval up to a maximum duration. The sleep * interval in seconds can be specified as the first argument * and the sleep duration as the second argument while running. * this class. The default sleep interval and sleep duration * are 5 seconds and 60 seconds, respectively. If these values * are less than zero, zero is used instead. */public class Job { // The job sleep interval public static final long DEFAULT_SLEEP_INTERVAL = 5; // The job sleep duration public static final long DEFAULT_SLEEP_DURATION = 60; public static void main(String[] args) { long sleepInterval = DEFAULT_SLEEP_INTERVAL; long sleepDuration = DEFAULT_SLEEP_DURATION; // Get the passed in sleep interval if (args.length >= 1) { sleepInterval = parseArg(args[0], DEFAULT_SLEEP_INTERVAL); if (sleepInterval < 0) { sleepInterval = 0; } } // Get the passed in the sleep duration if (args.length >= 2) { sleepDuration = parseArg(args[1], DEFAULT_SLEEP_DURATION); if (sleepDuration < 0) { sleepDuration = 0; } } long pid = ProcessHandle.current().getPid(); System.out.printf("Job (pid=%d) info: Sleep Interval" + "=%d seconds, Sleep Duration=%d " + "seconds.%n", pid, sleepInterval, sleepDuration); for (long sleptFor = 0; sleptFor < sleepDuration; http://www.cnblogs.com/IcanFixIt/p/7214359.html