Java9 进程API

1,923 阅读3分钟
原文链接: blog.csdn.net

1. 概述

Java中的进程API在Java5之前还是非常原始的,开启一个新进程唯一的方式是调用Runtime.getRuntime().exec() ,直到Java5发布之后,一些更加简明的开启新进程方式被封装到了ProcessBuilder 中。

而在Java9中,你也将能使用一种全新的方式来获取当前系统所有正在运行的进程的相关信息。

话不多说,先睹为快!

2. 当前Java进程的信息

我们通过调用java.lang.ProcessHandle.Info 可以获取诸多进程的相关信息:

  • 用于开启此进程所使用的命令
  • 命令中传递的参数
  • 开启时间
  • 开启者和总运行时间

下面是具体方式:

@Test
public void givenCurrentProcess_whenInvokeGetInfo_thenSuccess() 
  throws IOException {

    ProcessHandle processHandle = ProcessHandle.current();
    ProcessHandle.Info processInfo = processHandle.info();

    assertNotNull(processHandle.getPid());
    assertEquals(false, processInfo.arguments().isPresent());
    assertEquals(true, processInfo.command().isPresent());
    assertTrue(processInfo.command().get().contains("java"));
    assertEquals(true, processInfo.startInstant().isPresent());
    assertEquals(true, 
      processInfo.totalCpuDuration().isPresent());
    assertEquals(true, processInfo.user().isPresent());
}

需要注意的是,java.lang.ProcessHandle.Info 是在另一个接口java.lang.ProcessHandle中定义的公共接口。JDK 供应商(Oracle JDK, Open JDK, Zulu 或其他) 以上面这些方式来为接口提供实现,以使它们能返回进程的相关信息。

3. 产生进程的信息

我们也可以获取新产生的进程的相关信息。在此情况下,在我们调用java.lang.Process生成并获取一个进程之后,我们调用toHandle()方法来获取java.lang.ProcessHandle的实例。

其他细节与上例一致:

String javaCmd = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();

4. 枚举系统中的实时进程

我们可以列举出当前系统中的所有进程,这些进程对当前进程可见。返回的列表是调用API时的快照,因此在操作的同时可能有进程终止,也可能有新的进程被开启。

我们可以使用java.lang.ProcessHandle 接口中声明的静态方法allProcesses() 来获取ProcessHandle 流:

@Test
public void givenLiveProcesses_whenInvokeGetInfo_thenSuccess() {
    Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();
    liveProcesses.filter(ProcessHandle::isAlive)
      .forEach(ph -> {

        assertNotNull(ph.getPid());
        assertEquals(true, ph.info()
          .command()
          .isPresent());
      });
}

5. 枚举子进程

这里有两种获取方式:

  • 获取当前进程的直属子级
  • 获取当前进程的所有子级

前者调用children() ,后者调用descendants()

@Test
public void givenProcess_whenGetChildProcess_thenSuccess() 
  throws IOException{

    int childProcessCount = 5;
    for (int i = 0; i < childProcessCount; i++){
        String javaCmd = ProcessUtils.getJavaCmd()
          .getAbsolutePath();
        ProcessBuilder processBuilder 
          = new ProcessBuilder(javaCmd, "-version");
        processBuilder.inheritIO().start();
    }
    Stream<ProcessHandle> children
      = ProcessHandle.current().children();

    children.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}",
        ph.getPid(), ph.info().command()));

    // and for descendants
    Stream<ProcessHandle> descendants
      = ProcessHandle.current().descendants();
    descendants.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}",
        ph.getPid(), ph.info().command()));
}

6. 进程终止时触发相关操作

有时候有需求需要我们在某个进程终止时触发某些操作,我们可以通过调用java.lang.ProcessHandle 接口中声明的onExit() 方法来满足。 该方法返回CompletableFuture,可以在CompletableFuture完成时触发某些操作。

CompletableFuture 表示一个进程已经完成,它不关心进程以何种方式完成。通过显式调用它的get() 方法来等待进程完成:

@Test
public void givenProcess_whenAddExitCallback_thenSuccess() 
  throws Exception {

    String javaCmd = ProcessUtils.getJavaCmd()
      .getAbsolutePath();
    ProcessBuilder processBuilder 
      = new ProcessBuilder(javaCmd, "-version");
    Process process = processBuilder.inheritIO()
      .start();
    ProcessHandle processHandle = process.toHandle();

    log.info("PID: {} has started", processHandle.getPid());
    CompletableFuture<ProcessHandle> onProcessExit 
      = processHandle.onExit();
    onProcessExit.get();
    assertEquals(false, processHandle.isAlive());
    onProcessExit.thenAccept(ph -> {
        log.info("PID: {} has stopped", ph.getPid());
    });
}

当然,也可以通过java.lang.Process 接口来调用onExit()方法。

原文:www.baeldung.com/java-9-proc…