Java9 进程API详细介绍

11 篇文章 13 订阅

官方在JEP 102中引进新的进程API来增强java.lang.Process 类,并且引进java.lang.ProcessHandle 及其嵌套接口Info 来让开发者逃离时常因为要获取一个本地进程的PID而不得不使用本地代码的窘境。本文将详细介绍这些新特性。

一张逼格满满的图

1、ProcessHandle 与 ProcessHandle.Info

Java 9 为抽象Process 类增加了许多新方法,通过这些方法可以识别直接子进程与所有后代进程, 获取进程的PID、 获取进程的快照、获取CompletableFuture 实例来接收进程结束时的异步通知,以及更多特性的获取:

Stream<ProcessHandle> children()
Stream<ProcessHandle> descendants()
long getPid()
ProcessHandle.Info info()
CompletableFuture<Process> onExit()
boolean supportsNormalTermination()
ProcessHandle toHandle()

可以看出,超过半数的方法是需要结合ProcessHandle 接口来使用的, ProcessHandle 接口可以识别并控制本地进程。例如,toHandle() 方法可以返回ProcessHandle 的具体实现对象和与之关联的进程,ProcessHandle 中声明的方法如下:

static Stream<ProcessHandle> allProcesses()
Stream<ProcessHandle> children()
int compareTo(ProcessHandle other)
static ProcessHandle current()
Stream<ProcessHandle> descendants()
boolean destroy()
boolean destroyForcibly()
long getPid()
ProcessHandle.Info info()
boolean isAlive()
static Optional<ProcessHandle> of(long pid)
CompletableFuture<ProcessHandle> onExit()
Optional<ProcessHandle> parent()
boolean supportsNormalTermination()

Process 中的方法以方法名通过调用toHandle() 方法委派给ProcessHandle 接口。例如,调用getPid() 是调用 toHandle().getPid(),调用info()是调用 toHandle().info(),返回的是ProcessHandle.Info 对象,其嵌套接口Info 提供以下方法列表:

Optional<String[]> arguments()
Optional<String> command()
Optional<String> commandLine()
Optional<Instant> startInstant()
Optional<Duration> totalCpuDuration()
Optional<String> user()

每个方法返回java.util.Optional 实例,可能是null或非空对象引用, 大家都知道这样能有效避免java.lang.NullPointerException。您将在下文详细了解这些方法。

2、获取PID

Processlong getPid() 方法返回特定进程的PID。 之所以返回值是long类型而不是int类型,是因为PID是无符号整型。 最大的正值整型约为200万,但是Linux系统可以容纳大概400万个PID。

下面是获取PID的方式:

import java.io.IOException;

public class ProcessDemo
{
   public static void main(String[] args) throws IOException
   {
      Process p = new ProcessBuilder("notepad.exe").start();
      System.out.println(p.getPid());
   }
}

java.lang.ProcessBuilder 类(引进于Java 5) 为Windows的notepad.exe程序构建了一个进程。用start() 方法开启,返回了一个Process 对象来与新进程进行交互。 然后再调用getPid() 方法来获取PID。

产生新进程的方式

在Java 5之前,产生新进程的唯一方式是使用Runtime.getRuntime().exec() ,而现在更好的方式是使用ProcessBuilder

编译ProcessDemo.java:

javac ProcessDemo.java

运行ProcessDemo.java:

java ProcessDemo

你可以在进程管理器看到一个新的进程notepad.exe 正在运行,并且可以看到它的PID。

9480 或者其他无符号整型

从进程句柄获取PID

如果有一个ProcessHandle 对象,可以通过调用getPid() 来获取PID。

你一定很想知道在调用这个方法的时候如果进程无法启动或者已经意外终止会发生什么,第一种情况, start() 抛出 java.io.IOException ,第二种情况, getPid() 继续在进程终止后返回PID。

3、获取进程信息

ProcessHandle.Info 定义了一些返回进程信息的方法,比如,进程的可执行路径名,进程的开启时间,开启进程的用户等等。

以下代码开启了一个进程,并将此进程的一些信息输出:

import java.io.IOException;

import java.time.Duration;
import java.time.Instant;

public class ProcessDemo
{
   public static void main(String[] args) 
      throws InterruptedException, IOException
   {
      dumpProcessInfo(ProcessHandle.current());
      Process p = new ProcessBuilder("notepad.exe", "C:\\temp\\names.txt").start();
      dumpProcessInfo(p.toHandle());
      p.waitFor();
      dumpProcessInfo(p.toHandle());
   }

   static void dumpProcessInfo(ProcessHandle ph)
   {
      System.out.println("PROCESS INFORMATION");
      System.out.println("===================");
      System.out.printf("Process id: %d%n", ph.getPid());
      ProcessHandle.Info info = ph.info();
      System.out.printf("Command: %s%n", info.command().orElse(""));
      String[] args = info.arguments().orElse(new String[]{});
      System.out.println("Arguments:");
      for (String arg: args)
         System.out.printf("   %s%n", arg);
      System.out.printf("Command line: %s%n", info.commandLine().orElse(""));
      System.out.printf("Start time: %s%n", 
                        info.startInstant().orElse(Instant.now()).toString());
      System.out.printf("Run time duration: %sms%n",
                        info.totalCpuDuration()
                            .orElse(Duration.ofMillis(0)).toMillis());
      System.out.printf("Owner: %s%n", info.user().orElse(""));
      System.out.println();
   }
}

main() 方法里面首先调用 ProcessHandle.current() 来获取当前进程的句柄,然后用dumpProcessInfo() 方法来输出dump进程的详细信息。接下来启动notepad.exe ,并且dump其进程信息。等到notepad.exe 终止之后,再一次dump了它的信息。

dumpProcessInfo() 方法首先输出头标识信息,随后输出PID,随后获取ProcessHandle.Info 引用。接下来,调用command()和其他Info方法,输出它们的值。 如果方法返回null(因为信息不可用),则通过OptionalorElse()方法来返回信息。

以下是输出内容,期间可以看到notepad窗口:

PROCESS INFORMATION
===================
Process id: 1140
Command: C:\PROGRA~1\Java\jdk-9\bin\java.exe
Arguments:
Command line: 
Start time: 2017-03-02T22:24:40.998Z
Run time duration: 890ms
Owner: jeff\jeffrey

PROCESS INFORMATION
===================
Process id: 5516
Command: C:\Windows\System32\notepad.exe
Arguments:
Command line: 
Start time: 2017-03-02T22:24:41.763Z
Run time duration: 0ms
Owner: jeff\jeffrey

PROCESS INFORMATION
===================
Process id: 5516
Command: 
Arguments:
Command line: 
Start time: 2017-03-02T22:24:41.763Z
Run time duration: 234ms
Owner: jeff\jeffrey

第三部分的PROCESS INFORMATION迟迟没有出现,直到notepad界面消失才出现。Info的arguments() 方法没有向C:\temp\names.txt 文件里打印命令行,可能是因为此时信息是不可用的,抑或是因为出现了bug。在进程结束之后,command()方法返回null。最后,当command()方法或者arguments() 方法其中之一返回了null,commandLine()方法也将返回null。

4、获取所有进程的信息

ProcessHandle 中的allProcesses() 方法以Java8中Stream API的方式返回当前系统中所有可见的进程句柄。下面的代码展示了如何使用Stream来获取进程句柄, 取前四个进程,dump出它们的信息。

import java.io.IOException;

import java.time.Duration;
import java.time.Instant;

public class ProcessDemo
{
   public static void main(String[] args)
   {
      ProcessHandle.allProcesses()
                   .filter(ph -> ph.info().command().isPresent())
                   .limit(4)
                   .forEach((process) -> dumpProcessInfo(process));
   }

   static void dumpProcessInfo(ProcessHandle ph)
   {
      System.out.println("PROCESS INFORMATION");
      System.out.println("===================");
      System.out.printf("Process id: %d%n", ph.getPid());
      ProcessHandle.Info info = ph.info();
      System.out.printf("Command: %s%n", info.command().orElse(""));
      String[] args = info.arguments().orElse(new String[]{});
      System.out.println("Arguments:");
      for (String arg: args)
         System.out.printf("   %s%n", arg);
      System.out.printf("Command line: %s%n", info.commandLine().orElse(""));
      System.out.printf("Start time: %s%n", 
                        info.startInstant().orElse(Instant.now()).toString());
      System.out.printf("Run time duration: %sms%n",
                        info.totalCpuDuration()
                            .orElse(Duration.ofMillis(0)).toMillis());
      System.out.printf("Owner: %s%n", info.user().orElse(""));
      System.out.println();
   }
}

main()方法中调用allProcesses()方法,从路径名显示地将线程句柄流链式导入到一个filter之后再包装到一个新的线程句柄流。 (在我的环境中,当进程终止之后就不会打印出路径) limit(4) 方法可以截取不超过4个进程来放入流中。最后,迭代出它们的所有信息。

以下是输出结果:

PROCESS INFORMATION
===================
Process id: 8036
Command: C:\Windows\explorer.exe
Arguments:
Command line: 
Start time: 2017-03-02T16:21:14.436Z
Run time duration: 299328ms
Owner: jeff\jeffrey

PROCESS INFORMATION
===================
Process id: 10200
Command: C:\Windows\System32\dllhost.exe
Arguments:
Command line: 
Start time: 2017-03-02T16:21:16.255Z
Run time duration: 2000ms
Owner: jeff\jeffrey

PROCESS INFORMATION
===================
Process id: 1544
Command: C:\Program Files (x86)\WNSS\WNSS.exe
Arguments:
Command line: 
Start time: 2017-03-02T16:21:21.708Z
Run time duration: 862375ms
Owner: jeff\jeffrey

PROCESS INFORMATION
===================
Process id: 8156
Command: C:\Users\jeffrey\AppData\Local\SweetLabs App Platform\Engine\ServiceHostAppUpdater.exe
Arguments:
Command line: 
Start time: 2017-03-02T16:21:24.302Z
Run time duration: 2468ms
Owner: jeff\jeffrey

ProcessHandle的 children() 方法和 descendents() 方法运行结果很像allProcesses() f方法,除了它们是否是静态方法,以及返回值类型不一样之外,它们之间有一个集合从属关系,children() 是descendents()的子集,descendents()是allProcesses()的子集。

5、进程终止的触发机制

最后, ProcessHandle的 onExit() 方法返回java.util.concurrent.CompletableFuture让进程在终止时进行同步或异步操作成为可能。

在一个进程终止的时候打印出它的PID:

import java.io.IOException;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ProcessDemo
{
   public static void main(String[] args) 
      throws ExecutionException, InterruptedException, IOException
   {
      Process p = new ProcessBuilder("notepad.exe").start();
      ProcessHandle ph = p.toHandle();
      CompletableFuture<ProcessHandle> onExit = ph.onExit();
      onExit.get();
      onExit.thenAccept(ph_ -> System.out.printf("PID %d terminated%n", ph_.getPid()));
   }
}

main() 方法首先创建了一个notepad.exe进程。随后,又获取了这个进程的句柄,用这个句柄又得到了CompletableFuture。onExit.get() 在 main()获取到进程终止信号后会进行一些操作。

PID 7460 terminated

6、结论

Java 9的 Process API 增强早就该和Java一起出现并受到欢迎。虽然这盘文章介绍了一些它的API,但是更多的还是需要你去探索,比如,supportsNormalTermination() 方法或者parent() 方法的用法等等。

原文:https://www.javaworld.com/article/3176874/java-language/java-9s-other-new-enhancements-part-3.html

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Java进程间通信可以通过多种方式实现。其中一种常用的方式是使用Java的RMI(远程方法调用)机制。RMI是一种用于实现远程过程调用(RPC)的Java API,它能够直接传输序列化后的Java对象和支持分布式垃圾收集。RMI的实现依赖于Java虚拟机(JVM),因此它只支持从一个JVM到另一个JVM的调用,可以看作是RPC的Java版本。\[2\] 除了RMI,还有其他一些进程间通信的方式,包括数据传输、资源共享、通知事件和进程控制。数据传输是指一个进程需要将它的数据发送给另一个进程;资源共享是指多个进程之间共享同样的资源;通知事件是指一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件;进程控制是指有些进程希望完全控制另一个进程的执行,例如Debug进程,控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。这些方式可以根据具体的需求选择合适的方式来实现进程间通信。\[3\] #### 引用[.reference_title] - *1* [Java进程通信](https://blog.csdn.net/kking_edc/article/details/108338473)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Java通信方式总结](https://blog.csdn.net/qq_38233258/article/details/123615724)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值