A new Process API was introduced in JEP 102 to enhance the java.lang.Process class, and java.lang.ProcessHandle and its nested interface Info were introduced to help developers avoid having to use native code to get a PID from a native Process. This article covers these new features in detail.

ProcessHandle and processhandle.info

Java 9 adds a number of new methods to the abstract Process class to identify direct child processes with all descendant processes, get a Process PID, get a snapshot of the Process, get an instance of CompletableFuture to receive asynchronous notification of Process termination, and more features:

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

As you can see, more than half of these methods are used in conjunction with the ProcessHandle interface, which identifies and controls local processes. For example, the toHandle() method can return the concrete implementation object of ProcessHandle and the process associated with it. The methods declared in ProcessHandle are as follows:

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()Copy the code

Methods in Process delegate to the ProcessHandle interface with the method name by calling the toHandle() method. For example, getPid() is called toHandle().getpid (), and info() is called toHandle().info(), which returns the processhandle.info object, whose nested interface info provides the following list of methods:

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

Each method returns a Java. Util. Optional instance, may be null or the empty object references, we all know that this can effectively avoid the Java. Lang. NullPointerException. You’ll learn more about these methods below.

2. Obtain PID

The long getPid() method of Process returns the PID of a particular Process. The return value is long rather than int because PID is an unsigned integer. The largest positive integer is about 2 million, but Linux systems can hold about 4 million Pids.

Here’s how to get the 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()); }}Copy the code

The java.lang.ProcessBuilder class (introduced in Java 5) builds a process for the Notepad.exe program for Windows. Started with the start() method, it returns a Process object to interact with the new Process. The getPid() method is then called to get the PID.

The way in which new processes are generated

Prior to Java 5, the only way to generate a new process was to use Runtime.geTruntime ().exec(), but now a better way is to use ProcessBuilder.

Compile ProcessDemo. Java:

javac ProcessDemo.javaCopy the code

Run ProcessDemo. Java:

java ProcessDemoCopy the code

You can see in the process Manager that a new process notepad.exe is running and you can see its PID.

9480 or any other unsigned integer

Gets the PID from the process handle

If you have a ProcessHandle object, you can get the PID by calling getPid().

You must be wondering what happens if the process fails to start or terminates unexpectedly when this method is called. In the first case, start() throws a java.io.ioException, and in the second case, getPid() continues to return PID after the process terminates.

3. Obtain process information

Processhandle.info defines methods that return information about the process, such as the executable path name of the process, when the process was started, who started the process, and so on.

The following code starts a process and prints some information about the process:

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(a);
      dumpProcessInfo(p.toHandle());
      p.waitFor(a);
      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(a);
      System.out.printf("Command: %s%n", info.command(a).orElse(""));
      String[] args = info.arguments(a).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(a).orElse(""));
      System.out.printf("Start time: %s%n", 
                        info.startInstant(a).orElse(Instant.now()).toString());
      System.out.printf("Run time duration: %sms%n",
                        info.totalCpuDuration(a).orElse(Duration.ofMillis(0)).toMillis());
      System.out.printf("Owner: %s%n", info.user(a).orElse(""));
      System.out.println(a);}}Copy the code

The main() method first calls processhandle.current () to get a handle to the current process, and the dumpProcessInfo() method outputs details about the dump process. Start notepad.exe and dump its process information. After notepad. Exe terminates, dump its information again.

The dumpProcessInfo() method first outputs header identification information, then PID, and then gets the processhandle.info reference. Next, call command() and the other Info methods and print their values. If the method returns NULL (because the information is not available), the information is returned through the Optional orElse() method.

Here is the output, during which you can see the Notepad window:

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 \ JeffreyPROCESS 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 \ JeffreyPROCESS INFORMATION
===================
Process id: 5516
Command: 
Arguments:
Command line: 
Start time: 2017-03-02T22:24:41.763Z
Run time duration: 234ms
Owner: jeff\jeffreyCopy the code

The third part of the PROCESS INFORMATION did not appear until the Notepad interface disappeared. The arguments() method of Info does not print the command line to the C: temp\names.txt file, either because the information is not available at this point, or because there is a bug. After the process terminates, the command() method returns NULL. Finally, when either the command() or arguments() methods return NULL, the commandLine() method also returns NULL.

4. Get information about all processes

The allProcesses() method in ProcessHandle returns all visible process handles in the current system in the manner of the Stream API in Java8. The following code shows how to use Stream to get a process handle, take the first four processes, and dump their information.

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(a);
      System.out.printf("Command: %s%n", info.command().orElse(""));
      String[] args = info.arguments(a).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(a); }}Copy the code

The allProcesses() method is called in the main() method to chain the thread handle stream explicitly from the pathname into a filter, which is then wrapped into a new thread handle stream. (In my environment, the path is not printed when the process terminates.) The limit(4) method can intercept no more than four processes and put them into the stream. Finally, iterate out all of their information.

Here is the output:

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 \ JeffreyPROCESS 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 \ JeffreyPROCESS 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 \ JeffreyPROCESS 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 \ JeffreyCopy the code

The Children () and Descendents () methods of ProcessHandle run much like allProcesses() f, except that they are static and return value types are different. There is a set dependency between them. Children () is a subset of Descendents (), and Descendents () is a subset of allProcesses().

Trigger mechanism for process termination

Finally, the ProcessHandle onExit () method returns the java.util.concurrent.Com pletableFuture let the process terminates for synchronous or asynchronous operation is possible.

Prints the PID of a process when it terminates:

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(a);
      ProcessHandle ph = p.toHandle(a);
      CompletableFuture<ProcessHandle> onExit = ph.onExit(a);
      onExit.get(a);
      onExit.thenAccept(ph_ -> System.out.printf("PID %d terminated%n", ph_.getPid()));}}Copy the code

The main() method first creates a notepad.exe process. We then get a handle to the process, and with that handle we get the CompletableFuture. Onexit.get () does something after main() gets the process termination signal.

PID 7460 terminatedCopy the code

6, the conclusion

Java 9’s Process API enhancements are long overdue and welcome along with Java. Although this article introduced some of its API, but more still need you to explore, for example, supportsNormalTermination () method or the parent () method of usage and so on.