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),就是在Process
和ProcessBuilder
類中添加幾個(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è)返回ProcessHandle
的toHandle()
方法。
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í)候看到ProcessHandle
和ProcessHandle.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