JEP 102: Process API Updates enhances the java.lang.Process
class and introduces the java.lang.ProcessHandle
interface with its nested Info
interface to overcome limitations that often force developers to resort to native code; for example, to obtain the native process ID (PID). This post introduces you to these upgrades.
Enhancing Process, and introducing ProcessHandle and ProcessHandle.Info
Java 9 adds several new methods to the abstract Process
class that let you identify direct child or descendent processes, obtain this Process
's PID, return a snapshot of information about this Process
, obtain a completable future to receive asynchronous notification when this Process
exits, and more:
Stream<ProcessHandle> children()
Stream<ProcessHandle> descendants()
long getPid()
ProcessHandle.Info info()
CompletableFuture<Process> onExit()
boolean supportsNormalTermination()
ProcessHandle toHandle()
More than half of these methods work with the new ProcessHandle
interface, which identifies and provides control of native processes. For example, toHandle()
returns an object whose class implements ProcessHandle
, and which is associated with this Process
. ProcessHandle
's methods are listed below:
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()
Various Process
methods delegate to their ProcessHandle
counterparts by invoking toHandle()
followed by the method name. For example, getPid()
invokes toHandle().getPid()
and info()
invokes toHandle().info()
, which returns a ProcessHandle.Info
object. The nested Info
interface provides the following methods:
Optional<String[]> arguments()
Optional<String> command()
Optional<String> commandLine()
Optional<Instant> startInstant()
Optional<Duration> totalCpuDuration()
Optional<String> user()
Each method returns a java.util.Optional
instance that may contain a non-null object reference or null, and is useful for avoiding java.lang.NullPointerException
. You'll learn how to work with these methods along with various ProcessHandle
and new Process
methods in subsequent sections.
Obtaining the PID
Process
's long getPid()
method returns the PID of the invoking process. The method's return type is long
instead of int
because PIDs are unsigned integers, the largest positive int
value is around 2 million, and Linux can accommodate PIDs up to around 4 million. Listing 1 demonstrates getPid()
.
Listing 1. Obtaining and outputting a 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());
}
}
The java.lang.ProcessBuilder
class (introduced in Java 5) constructs a process builder for the Windows notepad.exe
program. The start()
method is invoked to start notepad.exe
, returning a Process
object to interact with the new process. Process
's getPid()
method is subsequently invoked on the Process
object and its value is output.
Compile Listing 1 as follows:
javac ProcessDemo.java
Run the resulting application as follows:
java ProcessDemo
I observed a new window for notepad.exe
along with an unsigned integer that varies from run to run. Here's an example:
9480
Perhaps you're wondering what happens with getPid()
when the process cannot be started or it terminates before this method is called. In the first case, start()
throws java.io.IOException
. In the second case, getPid()
continues to return the PID after the process terminates.
Obtaining process information
ProcessHandle.Info
defines several methods that return process information, such as the process's executable pathname, the process's start time, and the process's user. Listing 2 presents the source code to an application that dumps this and other information on the current process and another process to the standard output.
Listing 2. Obtaining and outputting process information
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()
first invokes ProcessHandle.current()
to obtain the current process's handle and passes this handle to a dumpProcessInfo()
invocation to dump information about this process. It next launches notepad.exe
, dumping its process information. After waiting for notepad.exe
to terminate, main()
dumps its information a second time.
dumpProcessInfo()
first outputs a header, outputs the PID, and obtains the process handle's Info
object. Next, it invokes command()
and other Info
methods, outputting their values. If the method would return null (because the information isn't available), the value passed to Optional
's orElse()
method is returned instead.
Compile Listing 2 and run the resulting application. I observed a new window for notepad.exe
, and also observed the following output from one run:
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
The third PROCESS INFORMATION
section doesn't appear until the window is dismissed. Info
's arguments()
method doesn't return the C:\temp\names.txt
command-line argument, possibly because the information isn't available, or possibly because of a bug. After the process terminates, command()
returns null. Finally, commandLine()
returns null when either command()
or arguments()
returns null.
Obtaining information on all processes
ProcessHandle
's allProcesses()
method returns a Java 8 Streams API stream of process handles describing all processes visible to the current process (that is, the process invoking allProcesses()
). Listing 3's application source code uses Streams to obtain these handles and, for each of the first four handles, dump information about the process.
Listing 3. Obtaining and outputting information on four processes visible to the current process
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()
invokes allProcesses()
and chains the resulting process handles stream to a filter that yields a new stream of process handles where a process executable pathname is present. (On my platform, the pathname isn't present when the process has terminated.) The limit(4)
call yields a truncated stream of no more than four process handles. Finally, the forEach()
call invokes dumpProcessInfo()
on each process handle.
Compile Listing 3 and run the resulting application. I observed the followed output from one run:
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
's children()
and descendents()
methods behave similarly to allProcesses()
except that they are non-static
and, respectively, return process handles for direct children of and direct children plus their descendents (and their descendents, and so on) of the current process.
Triggering actions on process termination
Finally, ProcessHandle
's onExit()
method makes it possible for a thread to use this method's returned java.util.concurrent.CompletableFuture
object to trigger actions to run synchronously or asynchronously when the process on which they depend terminates. Check out Listing 4.
Listing 4. Obtaining and outputting a process's PID when the process 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();
ProcessHandle ph = p.toHandle();
CompletableFuture<ProcessHandle> onExit = ph.onExit();
onExit.get();
onExit.thenAccept(ph_ -> System.out.printf("PID %d terminated%n",
ph_.getPid()));
}
}
main()
first creates and starts a notepad.exe
process. Next, it obtains this process's handle and uses this handle to obtain a completable future for the process. The onExit.get()
call causes main()
to wait for process termination, at which point thenAccept()
executes its lambda, producing output similar to that shown here:
PID 7460 terminated
Conclusion
Java 9's Process API enhancements are a long overdue and welcome addition to Java. Although I've finished my introduction to these enhancements, there's more for you to explore. For example, check out Process
's and ProcessHandle
's supportsNormalTermination()
method, and ProcessHandle
's parent()
method.
The following software was used to develop the post's code:
- 64-bit JDK 9ea+154
The post's code was tested on the following platform(s):
- JVM on 64-bit Windows 8.1
This story, "Java 9's other new enhancements, Part 3: The Process API" was originally published by JavaWorld.