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中有一些改进,就是在ProcessProcessBuilder类中添加几个方法。

在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类包含一个返回ProcessHandletoHandle()方法。

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()返回进程的用户。

现在是时候看到ProcessHandleProcessHandle.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

延伸阅读

向着Java前进-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训向着Java前进