Java 9 Process API: The Shape of Things to Come

Written on March 12, 2017

The JDK API for managing and controlling native Processes is getting a big update in Java 9. This article tells what’s changed and why it’s a great thing for the future of Java.

A New Process API

The first question that you might be wondering about the Java Process API is why on earth do Java programmers need to worry about other processes? Isn’t it enough to just care about your own process and ignore all the others? Modern software development has become increasingly concerned about its runtime and deployment concerns. Historically developers might just raise a ticket with an “ops” team to tell them to deploy the latest version of their software. Now many teams automate their build and deploy environments, deliver automatically updating software to their customers and can go from development machine to production with just a git push. Devops, SREs, doesn’t matter what you call it, it’s a good thing.

This automation of operations processes requires tooling that can inspect processes, control and manage them. With the legacy Java Process API it’s no wonder that tools like Chef, Puppet or Ansible are written in Ruby and Python. Now we’re not saying that adding a couple of classes will magically cause a rewrite of those tools, but it certainly means that Java developers who want to write tooling around monitoring, management and deployment of their production applications will have a much easier time.

A Process ID of My Own

Getting your own process id is an example of something that programmers sometimes need to do and see how it becomes less error prone and more reliable with Java 9. There was no official way to do this before, but you parse a String returned by ManagementFactory.getRuntimeMXBean() as a hacky workaround. As Github shows this is a thing that is used in literally thousands of open source projects. In Java 9 we just write the following.

ProcessHandle.current().getPid()

What’s happening here is that we use ProcessHandle.current() to get the ProcessHandle reference for our own process. ProcessHandle is a new interface that exposes a lot of functionality for inspecting and understanding processes and you’ll get to learn about this functionality in the remainder of the article. The method getPid() on ProcessHandle returns the process id of the process in question.

Kill All The Processes!

Ok, that was nice and simple, but what about building a more complex example tool using the new API? We all love to beta test software, like the Chrome canary builds but sometimes this kind of software hangs, leaving a bunch of processes around that we need to terminate.

In order to solve this problem we need to do a few different things.

Find all the currently running processes. Decide which of them are related to the hung application that we want to terminate Terminate the process Tell the user that the processes have been terminated.

Our implementation uses the ProcessHandle.allProcesses() method in order to get a Stream of the running processes. It then filters their commands by name, using the isApplication() method that we will discuss in a bit. Finally it uses the ProcessHandle.destroyForcibly() method in order to kill the process. The full code can be seen below.

void cleanupApplication(String applicationName)
{
  ProcessHandle
    .allProcesses()
    .filter(process -> isApplication(applicationName, process))
    .forEach(process ->
      process.info().command().ifPresent(command ->
        closeAndLog(process, command)));

}

void closeAndLog(ProcessHandle process, String command)
{
   String status = process.destroyForcibly() ? " Success!" : " Failed";
   System.out.println("Killing ... " + command + status);
}

ProcessHandle has two different ways of killing processes - destroy() and destroyForcibly(). These are roughly equivalent to the difference between sending a SIGTERM and SIGKILL on a unix system. destroy() will request the process exit, destroyForcibly() will force it to terminate even if it want’s to stay around. We use the latter here as it deals with cases where our application process has hung more effectively.

The other thing to note in this code is getting the command name of the process in order to print it out. ProcessHandle.info() returns an Info object that can be queried for information. Now each query on the Info object may involve performing an underlying operation in order to find the required information, so they all return Optional results. Whilst this may seem like it makes the API a little bit more confusing at first it helps model the failure mode of these methods appropriately. Since these processes are running concurrently with this application they may exit between their ProcessHandle objects being snapshotted in allProcesses() and querying for their command. If that’s the case then we don’t know what the command name for the process is, so this is modelled by an optional.

Likewise there may be a problem in forcibly destroying the process, so we need to check the result of that method call.

We used the isApplication() method in order to check whether the ProcessHandle is the application that we’re looking for. Here it’s simply a matter of checking whether the executed command contains the name String that we’re looking for. This isn’t a perfect mechanism, for example someone could rename our binary program, run it and it would avoid being closed. In practice however this works fine for our purposes. Again, the command() method returns an Optional so we filter it in order to ensure that it matches the required application name.

boolean isApplication(final String appName, final ProcessHandle process)
{
   return process.info().command().filter(command ->
     command.contains(appName)).isPresent();
}

Here is an example of the output that running the program on my laptop results in.

java -cp target/classes/ com.iteratrlearning.blog_examples.process_api.ApplicationCleaner

Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/nacl_helper Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!
Killing ... /opt/google/chrome/chrome Success!

Finding our Long Running Processes

Another task that we might want to use the Process API for is to find all the processes owned by our user. We also want to sort the processes by how long they have been executing for and print those durations out. Here is what our program looks like:

final String userName = System.getProperty("user.name");
ProcessHandle
  .allProcesses()
  .map(ProcessHandle::info)
  .filter(info -> info.user().filter(name ->
    name.contains(userName)).isPresent())
  .sorted(Comparator.comparing(info ->
    info.totalCpuDuration().orElse(Duration.ZERO)))
  .forEach(info ->
    info.command().ifPresent(command ->
      info.totalCpuDuration().ifPresent(duration ->
        System.out.println(command + " has been running for " + duration))));

We’ll again start out by streaming all the processes running on the box. We are really mainly interested in getting the Info object related to the process - this is a snapshot of several properties about the running program - so we’ll map each ProcessHandle into its Info. We pull out the user that the program is executing as using the user() method and check that it’s the userName of our user. Next we sort the processes by duration, again the totalCpuDuration() method returns an Optional so have to use the aforementioned filter pattern. Finally we print out the relevant process information.

Conclusions

In this article we showed a few examples of practical programs that you could write using the Process API. If you want to explore the API on a method-by-method basis then the Javadoc is conveniently posted online for ProcessHandle and Process. Our code examples can be found on Github.

One of the interesting takeaway here is how newer Java APIs are beginning to adopt other core library features. This API uses the Optional and CompletableFuture classes extensively and it’s a trend that one can only expect to continue.

The improvements in the Process API offer Java programmers a much better story when it comes to writing automated tooling for deploying, managing and upgrading their runtime. This is an increasingly popular and important area as more teams move to a devops and SRE driven environment and away from manual system administration. That isn’t to say that Java is always the right tool for every project in that space, but it’s great to see work being put into to making things better in Java 9.