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.
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.
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
shows this is a thing that is used in literally thousands of open source
projects. In Java 9 we just write the following.
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
ProcessHandle returns the process id of the process in question.
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.
ProcessHandle has two different ways of killing processes -
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.
Here is an example of the output that running the program on my laptop results in.
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:
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.
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
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.