Tips
做一個(gè)終身學(xué)習(xí)的人。

在本章中,主要介紹以下內(nèi)容:

  • Process API是什么

  • 如何創(chuàng)建本地進(jìn)程

  • 如何獲取新進(jìn)程的信息

  • 如何獲取當(dāng)前進(jìn)程的信息

  • 如何獲取所有系統(tǒng)進(jìn)程的信息

  • 如何設(shè)置創(chuàng)建,查詢和管理本地進(jìn)程的權(quán)限

一. Process API是什么

Process API 由接口和類組成,用來與本地進(jìn)程一起工作,使用API,可以做以下事情:

  • 從Java代碼中創(chuàng)建新的本地進(jìn)程

  • 獲取本地進(jìn)程的進(jìn)程句柄,無論它們是由Java代碼還是通過其他方式創(chuàng)建

  • 銷毀運(yùn)行進(jìn)程

  • 查詢活動(dòng)的進(jìn)程及其屬性

  • 獲取進(jìn)程的子進(jìn)程和父進(jìn)程的列表

  • 獲取本地進(jìn)程的進(jìn)程ID(PID)

  • 獲取新創(chuàng)建的進(jìn)程的輸入,輸出和錯(cuò)誤流

  • 等待進(jìn)程終止

  • 當(dāng)進(jìn)程終止時(shí)執(zhí)行任務(wù)

Process API由java.lang包中的以下類和接口組成:

RuntimeProcessBuilderProcessBuilder.RedirectProcessProcessHandleProcessHandle.Info

自Java 1.0以來,支持使用本地進(jìn)程。Process類的實(shí)例表示由Java程序創(chuàng)建的本地進(jìn)程。 通過調(diào)用Runtime類的exec()方法啟動(dòng)一個(gè)進(jìn)程。

JDK 5.0添加了ProcessBuilder類,JDK 7.0添加了ProcessBuilder.Redirect的嵌套類。 ProcessBuilder類的實(shí)例保存一個(gè)進(jìn)程的一組屬性。 調(diào)用其start()方法啟動(dòng)本地進(jìn)程并返回一個(gè)表示本地進(jìn)程的Process類的實(shí)例。 可以多次調(diào)用其start()方法; 每次使用ProcessBuilder實(shí)例中保存的屬性啟動(dòng)一個(gè)新進(jìn)程。 在Java 5.0中,ProcessBuilder類接管Runtime.exec()方法來啟動(dòng)新進(jìn)程。

在Java 7和Java 8中的Process API中有一些改進(jìn),就是在ProcessProcessBuilder類中添加幾個(gè)方法。

在Java 9之前,Process API仍然缺乏對(duì)使用本地進(jìn)程的基本支持,例如獲取進(jìn)程的PID和所有者,進(jìn)程的開始時(shí)間,進(jìn)程使用了多少CPU時(shí)間,多少本地進(jìn)程正在運(yùn)行等。請(qǐng)注意,在Java 9之前,可以啟動(dòng)本地進(jìn)程并使用其輸入,輸出和錯(cuò)誤流。 但是,無法使用未啟動(dòng)的本地進(jìn)程,無法查詢進(jìn)程的詳細(xì)信息。 為了更緊密地處理本地進(jìn)程,Java開發(fā)人員不得不使用Java Native Interface(JNI)來編寫本地代碼。 Java 9使這些非常需要的功能與本地進(jìn)程配合使用。

Java 9向Process API添加了一個(gè)名為ProcessHandle的接口。 ProcessHandle接口的實(shí)例標(biāo)識(shí)一個(gè)本地進(jìn)程; 它允許查詢進(jìn)程狀態(tài)并管理進(jìn)程。

比較Process類和ProcessHandle接口。 Process類的一個(gè)實(shí)例表示由當(dāng)前Java程序啟動(dòng)的本地進(jìn)程,而ProcessHandle接口的實(shí)例表示本地進(jìn)程,無論是由當(dāng)前Java程序啟動(dòng)還是以其他方式啟動(dòng)。 在Java 9中,已經(jīng)在Process類中添加了幾種方法,這些方法也可以在新的ProcessHandle接口中使用。 Process類包含一個(gè)返回ProcessHandletoHandle()方法。

ProcessHandle.Info接口的實(shí)例表示進(jìn)程屬性的快照。 請(qǐng)注意,進(jìn)程由不同的操作系統(tǒng)不同地實(shí)現(xiàn),因此它們的屬性不同。 過程的狀態(tài)可以隨時(shí)更改,例如,當(dāng)進(jìn)程獲得更多CPU時(shí)間時(shí),進(jìn)程使用的CPU時(shí)間增加。 要獲取進(jìn)程的最新信息,需要在需要時(shí)使用ProcessHandle接口的info()方法,這將返回一個(gè)新的ProcessHandle.Info實(shí)例。

本章中的所有示例都在Windows 10中運(yùn)行。當(dāng)使用Windows 10或其他操作系統(tǒng)在機(jī)器上運(yùn)行這些程序時(shí),可能會(huì)得到不同的輸出。

二. 當(dāng)前進(jìn)程

ProcessHandle接口的current()靜態(tài)方法返回當(dāng)前進(jìn)程的句柄。 請(qǐng)注意,此方法返回的當(dāng)前進(jìn)程始終是正在執(zhí)行代碼的Java進(jìn)程。

// Get the handle of the current processProcessHandle current = ProcessHandle.current();

獲取當(dāng)前進(jìn)程的句柄后,可以使用ProcessHandle接口的方法獲取有關(guān)進(jìn)程的詳細(xì)信息。

Tips
你不能殺死當(dāng)前進(jìn)程。 嘗試通過使用ProcessHandle接口的destroy()destroyForcibly()方法來殺死當(dāng)前進(jìn)程會(huì)導(dǎo)致IllegalStateException異常。

三. 查詢進(jìn)程狀態(tài)

可以使用ProcessHandle接口中的方法來查詢進(jìn)程的狀態(tài)。 下表列出了該接口常用的簡(jiǎn)單說明方法。 請(qǐng)注意,許多這些方法返回執(zhí)行快照時(shí)進(jìn)程狀態(tài)的快照。 不過,由于進(jìn)程是以異步方式創(chuàng)建,運(yùn)行和銷毀的,所以當(dāng)稍后使用其屬性時(shí),所以無法保證進(jìn)程仍然處于相同的狀態(tài)。

方法描述
static Stream<ProcessHandle> allProcesses()返回操作系統(tǒng)中當(dāng)前進(jìn)程可見的所有進(jìn)程的快照。
Stream<ProcessHandle> children()返回進(jìn)程當(dāng)前直接子進(jìn)程的快照。 使用descendants()方法獲取所有級(jí)別的子級(jí)列表,例如子進(jìn)程,孫子進(jìn)程進(jìn)程等。返回當(dāng)前進(jìn)程可見的操作系統(tǒng)中的所有進(jìn)程的快照。
static ProcessHandle current()返回當(dāng)前進(jìn)程的ProcessHandle,這是執(zhí)行此方法調(diào)用的Java進(jìn)程。
Stream<ProcessHandle> descendants()返回進(jìn)程后代的快照。 與children()方法進(jìn)行比較,該方法僅返回進(jìn)程的直接后代。
boolean destroy()請(qǐng)求進(jìn)程被殺死。 如果成功請(qǐng)求終止進(jìn)程,則返回true,否則返回false。 是否可以殺死進(jìn)程取決于操作系統(tǒng)訪問控制。
boolean destroyForcibly()要求進(jìn)程被強(qiáng)行殺死。 如果成功請(qǐng)求終止進(jìn)程,則返回true,否則返回false。 殺死進(jìn)程會(huì)立即強(qiáng)制終止進(jìn)程,而正常終止則允許進(jìn)程徹底關(guān)閉。 是否可以殺死進(jìn)程取決于操作系統(tǒng)訪問控制。
long getPid()返回由操作系統(tǒng)分配的進(jìn)程的本地進(jìn)程ID(PID)。 注意,PID可以由操作系統(tǒng)重復(fù)使用,因此具有相同PID的兩個(gè)處理句柄可能不一定代表相同的過程。
ProcessHandle.Info info()返回有關(guān)進(jìn)程信息的快照。
boolean isAlive()如果此ProcessHandle表示的進(jìn)程尚未終止,則返回true,否則返回false。 請(qǐng)注意,在成功請(qǐng)求終止進(jìn)程后,此方法可能會(huì)返回一段時(shí)間,因?yàn)檫M(jìn)程將以異步方式終止。
static Optional<ProcessHandle> of(long pid)返回現(xiàn)有本地進(jìn)程的Optional<ProcessHandle>。 如果具有指定pid的進(jìn)程不存在,則返回空的Optional。
CompletableFuture <ProcessHandle> onExit()返回一個(gè)用于終止進(jìn)程的CompletableFuture<ProcessHandle>。 可以使用返回的對(duì)象來添加在進(jìn)程終止時(shí)執(zhí)行的任務(wù)。 在當(dāng)前進(jìn)程中調(diào)用此方法會(huì)引發(fā)IllegalStateException異常。
Optional<ProcessHandle> parent()返回父進(jìn)程的Optional<ProcessHandle>
boolean supportsNormalTermination()如果destroy()的實(shí)現(xiàn)正常終止進(jìn)程,則返回true。

下表列出ProcessHandle.Info嵌套接口的方法和描述。 此接口的實(shí)例包含有關(guān)進(jìn)程的快照信息。 可以使用ProcessHandle接口或Process類的info()方法獲取ProcessHandle.Info。 接口中的所有方法都返回一個(gè)Optional。

方法描述
Optional<String[]> arguments()返回進(jìn)程的參數(shù)。 該過程可能會(huì)更改啟動(dòng)后傳遞給它的原始參數(shù)。 在這種情況下,此方法返回更改的參數(shù)。
Optional<String> command()返回進(jìn)程的可執(zhí)行路徑名。
Optional<String> commandLine()它是一個(gè)進(jìn)程的組合命令和參數(shù)的便捷的方法。如果command()arguments()方法都沒有返回空Optional, 它通過組合從command()arguments()方法返回的值來返回進(jìn)程的命令行。
Optional<Instant> startInstant()返回進(jìn)程的開始時(shí)間。 如果操作系統(tǒng)沒有返回開始時(shí)間,則返回一個(gè)空Optional
Optional<Duration> totalCpuDuration()返回進(jìn)程使用的CPU時(shí)間。 請(qǐng)注意,進(jìn)程可能運(yùn)行很長(zhǎng)時(shí)間,但可能使用很少的CPU時(shí)間。
Optional<String> user()返回進(jìn)程的用戶。

現(xiàn)在是時(shí)候看到ProcessHandleProcessHandle.Info接口的實(shí)際用法。 本章中的所有類都在com.jdojo.process.api模塊中,其聲明如下所示。

// module-info.javamodule com.jdojo.process.api {
    exports com.jdojo.process.api;
}

接下來包含CurrentProcessInfo類的代碼。 它的printInfo()方法將ProcessHandle作為參數(shù),并打印進(jìn)程的詳細(xì)信息。 我們還在其他示例中使用此方法打印進(jìn)程的詳細(xì)信息。main()方法獲取運(yùn)行進(jìn)程的當(dāng)前進(jìn)程的句柄,這是一個(gè)Java進(jìn)程,并打印其詳細(xì)信息。 你可能會(huì)得到不同的輸出。 以下是當(dāng)程序在Windows 10上運(yùn)行時(shí)生成輸出。

// 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

四. 比較進(jìn)程

比較兩個(gè)進(jìn)程是否相等等或順序是否相同是棘手的。 不能依賴PID來處理相同的進(jìn)程。 操作系統(tǒng)在進(jìn)程終止后重用PID。 可以與PID一起檢查流程的開始時(shí)間;如果兩者相同,則兩個(gè)過程可能相同。 ProcessHandle接口的默認(rèn)實(shí)現(xiàn)的equals()方法檢查以下三個(gè)信息,以使兩個(gè)進(jìn)程相等:

  • 對(duì)于這兩個(gè)進(jìn)程,ProcessHandle接口的實(shí)現(xiàn)類必須相同。

  • 進(jìn)程必須具有相同的PID。

  • 進(jìn)程必須同一時(shí)間啟動(dòng)。

Tips
ProcessHandle接口中使用compareTo()方法的默認(rèn)實(shí)現(xiàn)對(duì)于排序來說并不是很有用。 它比較了兩個(gè)進(jìn)程的PID。

五. 創(chuàng)建進(jìn)程

需要使用ProcessBuilder類的實(shí)例來啟動(dòng)一個(gè)新進(jìn)程。 該類包含幾個(gè)方法來設(shè)置進(jìn)程的屬性。 調(diào)用start()方法啟動(dòng)一個(gè)新進(jìn)程。 start()方法返回一個(gè)Process對(duì)象,可以使用它來處理進(jìn)程的輸入,輸出和錯(cuò)誤流。 以下代碼段創(chuàng)建一個(gè)ProcessBuilder在Windows上啟動(dòng)JVM:

ProcessBuilder pb = new ProcessBuilder()
                    .command("C:\\java9\\bin\\java.exe",                             "--module-path",                             "myModulePath",                             "--module",                             "myModule/className")
                    .inheritIO();

有兩種方法來設(shè)置這個(gè)新進(jìn)程的命令和參數(shù):

  • 可以將它們傳遞給ProcessBuilder類的構(gòu)造函數(shù)。

  • 可以使用command()方法。

沒有參數(shù)的command()方法返回在ProcessBuilder中命令的設(shè)置的。 帶有參數(shù)的其他版本 —— 一個(gè)帶有一個(gè)String的可變參數(shù),一個(gè)帶有List<String>的版本,都用于設(shè)置命令和參數(shù)。 該方法的第一個(gè)參數(shù)是命令路徑,其余的是命令的參數(shù)。

新進(jìn)程有自己的輸入,輸出和錯(cuò)誤流。 inheritIO()方法將新進(jìn)程的輸入,輸出和錯(cuò)誤流設(shè)置為與當(dāng)前進(jìn)程相同。 ProcessBuilder類中有幾個(gè)redirectXxx()方法可以為新進(jìn)程定制標(biāo)準(zhǔn)I/O,例如將標(biāo)準(zhǔn)錯(cuò)誤流設(shè)置為文件,因此所有錯(cuò)誤都會(huì)記錄到文件中。 配置進(jìn)程的所有屬性后,可以調(diào)用start()來啟動(dòng)進(jìn)程:

// Start a new process
Process newProcess = pb.start();

可以多次調(diào)用ProcessBuilder類的start()方法來啟動(dòng)與之前保持的相同屬性的多個(gè)進(jìn)程。 這具有性能優(yōu)勢(shì),可以創(chuàng)建一個(gè)ProcessBuilder實(shí)例,并重復(fù)使用它來多次啟動(dòng)相同的進(jìn)程。

可以使用Process類的toHandle()方法獲取進(jìn)程的進(jìn)程句柄:

// Get the process handleProcessHandle handle = newProcess.toHandle();

可以使用進(jìn)程句柄來銷毀進(jìn)程,等待進(jìn)程完成,或查詢進(jìn)程的狀態(tài)和屬性,如其子進(jìn)程,后代,父進(jìn)程,使用的CPU時(shí)間等。有關(guān)進(jìn)程的信息,對(duì)進(jìn)程的控制取決于操作系統(tǒng)訪問控制。

創(chuàng)建可以在所有操作系統(tǒng)上運(yùn)行的進(jìn)程都很棘手。 可以創(chuàng)建一個(gè)新進(jìn)程啟動(dòng)新的JVM來運(yùn)行一個(gè)類。

如下包含一個(gè)Job類的代碼。 它的main()方法需要兩個(gè)參數(shù):睡眠間隔和睡眠持續(xù)時(shí)間(以秒為單位)。 如果沒有參數(shù)傳遞,該方法將使用5秒和60秒作為默認(rèn)值。 在第一部分中,該方法嘗試提取第一個(gè)和第二個(gè)參數(shù)(如果指定)。 在第二部分中,它使用ProcessHandle.current()方法獲取當(dāng)前進(jìn)程執(zhí)行此方法的進(jìn)程句柄。 它讀取當(dāng)前進(jìn)程的PID并打印包括PID,睡眠間隔和睡眠持續(xù)時(shí)間的消息。 最后,它開始一個(gè)for循環(huán),并持續(xù)休眠睡眠間隔,直到達(dá)到睡眠持續(xù)時(shí)間。 在循環(huán)的每次迭代中,它打印一條消息。

// 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