Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

The process API in Java had been quite primitive prior to Java 5, the only way to spawn a new process was to use the Runtime.getRuntime().exec() API. Then in Java 5, ProcessBuilder API was introduced which supported a cleaner way of spawning new processes.

Java 9 is adding a new way of getting information about current and any spawned processes.

In this article, we will look at both of these enhancements.

2. Current Java Process Information

We can now obtain a lot of information about the process via the API java.lang.ProcessHandle.Info API:

  • the command used to start the process
  • the arguments of the command
  • time instant when the process was started
  • total time spent by it and the user who created it

Here’s how we can do that:

private static void infoOfCurrentProcess() {
    ProcessHandle processHandle = ProcessHandle.current();
    ProcessHandle.Info processInfo = processHandle.info();

    log.info("PID: " + processHandle.pid());
    log.info("Arguments: " + processInfo.arguments());
    log.info("Command: " + processInfo.command());
    log.info("Instant: " + processInfo.startInstant());
    log.info("Total CPU duration: " + processInfo.totalCpuDuration());
    log.info("User: " + processInfo.user());
}

It is important to note that java.lang.ProcessHandle.Info is a public interface defined within another interface java.lang.ProcessHandle. The JDK provider (Oracle JDK, Open JDK, Zulu, or others) should provide implementations to these interfaces in such a way that these implementations return the relevant information for the processes.

The output depends on the operating system and Java version. Here’s an example of what the output can look like:

16:31:24.784 [main] INFO  c.b.j.process.ProcessAPIEnhancements - PID: 22640
16:31:24.790 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Arguments: Optional[[Ljava.lang.String;@2a17b7b6]
16:31:24.791 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Command: Optional[/Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home/bin/java]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Instant: Optional[2021-08-31T14:31:23.870Z]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Total CPU duration: Optional[PT0.818115S]
16:31:24.796 [main] INFO  c.b.j.process.ProcessAPIEnhancements - User: Optional[username]

3. Spawned Process Information

It is also possible to get the process information of a newly spawned process. In this case, after we spawn the process and get an instance of the java.lang.Process, we invoke the toHandle() method on it to get an instance of java.lang.ProcessHandle.

The rest of the details remain the same as in the section above:

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

4. Enumerating Live Processes in the System

We can list all the processes currently in the system, which are visible to the current process. The returned list is a snapshot at the time when the API was invoked, so it’s possible that some processes terminated after taking the snapshot or some new processes were added.

In order to do that, we can use the static method allProcesses() available in the java.lang.ProcessHandle interface which returns us a Stream of ProcessHandle:

private static void infoOfLiveProcesses() {
    Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();
    liveProcesses.filter(ProcessHandle::isAlive)
        .forEach(ph -> {
            log.info("PID: " + ph.pid());
            log.info("Instance: " + ph.info().startInstant());
            log.info("User: " + ph.info().user());
        });
}

5. Enumerating Child Processes

There are two variants to do this:

  • get direct children of the current process
  • get all the descendants of the current process

The former is achieved by using the method children() and the latter is achieved by using the method descendants():

private static void infoOfChildProcess() 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.pid(), ph.info()
        .command()));
    Stream<ProcessHandle> descendants = ProcessHandle.current()
      .descendants();
    descendants.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}", ph.pid(), ph.info()
        .command()));
}

6. Triggering Dependent Actions on Process Termination

We might want to run something on termination of the process. This can be achieved by using the onExit() method in the java.lang.ProcessHandle interface. The method returns us a CompletableFuture which provides the ability to trigger dependent operations when the CompletableFuture is completed.

Here, the CompletableFuture indicates the process has completed, but it doesn’t matter if the process has completed successfully or not. We invoke the get() method on the CompletableFuture, to wait for its completion:

private static void infoOfExitCallback() throws IOException, InterruptedException, ExecutionException {
    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.pid());
    CompletableFuture onProcessExit = processHandle.onExit();
    onProcessExit.get();
    log.info("Alive: " + processHandle.isAlive());
    onProcessExit.thenAccept(ph -> {
        log.info("PID: {} has stopped", ph.pid());
    });
}

The onExit() method is available in the java.lang.Process interface as well.

7. Conclusion

In this tutorial, we covered interesting additions to the Process API in Java 9 that give us much more control over the running and spawned processes.

The code used in this article can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!