Architecture & DesignUnderstanding Java Process and Java ProcessBuilder

Understanding Java Process and Java ProcessBuilder

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java has extensive API support to deal with not only lightweight processes (threads), but also significant support to deal with the processes that are called heavyweight due to their memory and resource footprints. The classes such as Process and other related classes defined in the java.lang package provide significant support in this respect, and leverage many core functionalities of the process in general. These classes are used primarily to gather information about and implement runtime processes. This article explicates some of the common functionalities of these APIs to understand processes in Java.

Learn JAVA and Start your Free Trial today!

The Runtime Class

Every Java program has an instance of the Runtime class, which encapsulates the runtime environment of the program. This class cannot be instantiated, but we can get a reference to the Runtime of the currently running program with the help of the static method called Runtime.getRuntime(). There are several methods defined in the Runtime class. These methods can be invoked to get the information about the runtime environment such as number of processors available to the JVM, about of memory available, loading native library, explicitly call garbage collector, and so forth.

Here is a quick example to illustrate the idea.

package org.mano.example;

public class ProcessDemo {

   public static void main(String[] args) throws Exception {

      Runtime r=Runtime.getRuntime();

      System.out.println("No of Processor: "+
         r.availableProcessors());
      System.out.println("Total memory: "+r.totalMemory());
      System.out.println("Free memory: "+r.freeMemory());
      System.out.println("Memory occupied: "+
         (r.totalMemory()-r.freeMemory()));

      for(int i=0;i<=10000;i++){
         new Object();
      }

      r.gc();

      System.out.println("::Memory status::");
      System.out.println("Total memory: "+r.totalMemory());
      System.out.println("Free memory: "+r.freeMemory());
      System.out.println("Memory occupied: "+
         (r.totalMemory()-r.freeMemory()));
   }
}

Apart from these, there is a list of the overloaded exec, method which returns a reference to a Process instance. The overloaded exec methods are as follows:

  • Process exec(String command)
  • Process exec(String command, String[] envp )
  • Process exec(String command, String[] envp, File dir )
  • Process exec(String[] cmdarray)
  • Process exec(String[] cmdarray, String[] envp )
  • Process exec(String[] cmdarray, String[] envp, File dir )

Note that the command parameter represents the command that we want to execute in a separate process and, in one of its array form variations, we can specify the command as well as the arguments passed with the command in a separate process. The envp argument specifies the environment variable to used by the command and the File parameter represents the working directory.

This method can be used to execute a separate process, as we shall see down the line.

The Process Class

The Process is an abstract class defined in the java.lang package that encapsulates the runtime information of a program in execution. The exec method invoked by the Runtime instance returns a reference to this class instance. There is an another way to create an instance of this class, through the ProcessBuilder.start() method.

The methods defined by the Process class can be used to perform input/output operations from the process, check the exist status of the process, wait for it to complete, and terminate the process. These methods, however, are not built to work on special processes of the native platform like daemon processes, shell scripts, and so on.

Intriguingly, the process created by the exec method does not own a console. Therefore, redirecting its input, output, and error in the standard file is ruled out. Instead, it redirects (stdin, stdout, stderr) to the parent process. If need be, we can access them via streams obtained using methods defined in the class, such as getInputStream(), getOutputStream() and getErrorSteam(). These are the ways we can feed input to and get results from the sub processes. However, there’s a catch: There the standard input and output buffers have a limited size as defined by the underlying platform. Unless the standard input and output streams are promptly written and read respectively of the sub process, it may block or deadlock the sub process.

Some of the common methods defined in this class are:

  • Exits code returned from the process executed
    • int exitValue()
  • Reads/writes output, error, and input streams to and from the process.
    • InputStream getErrorStream()
    • InputStream getInputStream()
    • OutputStream getOutputStream()
  • Checks to see if the invoking process is still running.
    • Boolean isAlive()
  • Waits for the invoking process to end. The integer value returned by the method is the exit code by the process. In another overloaded method, we can specify the wait time. This method returns true if the process has terminated and false if timeout has occurred.
    • int waitFor()
    • Boolean waitFor(long timeOut, TimeUnit unit)
  • These two methods are used to kill or terminate the process. One, the second, just does it forcibly.
    • void destroy()
    • Process destroyForcibly()

Let’s write a simple Java program to open an application as a separate process. After it is opened, the program would wait for, say, 10 seconds and then destroy the process, which will immediately close the application.

package org.mano.example;

import java.util.concurrent.TimeUnit;

public class ProcessDemo {

   public static void main(String[] args) throws Exception {

      Runtime r = Runtime.getRuntime();
      Process p = r.exec("firefox");
      p.waitFor(10, TimeUnit.SECONDS);
      p.destroy();
   }
}

The ProcessBuilder Class

This is an auxiliary class for the Process and is instantiated to manage a collection of process attributes. We can invoke the start method to create a new process with the attributes defined by the instance of the ProcessBuilder class. Repeated calls to the start method would create a new process with the same attributes. Note that ProcessBuilder is not a synchronized class; hence, if it is not synchronized explicitly, it not thread safe to access the instance of this class through multiple threads. As of Java 1.5, ProcessBuilder.start() is preferred way to create a process.

The ProcessBuilder class defines two constructors, such as:

ProcessBuilder(List<String> command)
ProcessBuilder(String... command)

The meaning implied by the parameters passed to both constructors is same. In the first constructor, the command to be executed, along with command line arguments, is passed in a list of strings. And, in the second constructor, the command and the command line arguments are specified through the varargs parameter. We can use either of the constructors, depending upon the way to pass the parameter.

Here’s an example on how we can run a Linux command say, cal 2022, capture the output in an input stream, and display it.

package org.mano.example;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ProcessDemo {

   public static void main(String[] args) {

      System.out.println
         ("*************Calendar for Year**********");
      try {
         ProcessBuilder pb = new
            ProcessBuilder("cal", "2022");
         final Process p=pb.start();
         BufferedReader br=new BufferedReader(
            new InputStreamReader(
               p.getInputStream()));
            String line;
            while((line=br.readLine())!=null){
               System.out.println(line);
            }
      } catch (Exception ex) {
         System.out.println(ex);
      }
      System.out.println
         ("************************************");
   }
}

The preceding program can be modified to redirect the output of the command to a file. Note that this can be done in several ways. Here is one of the ways, without changing much of the previous code.

package org.mano.example;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;

public class ProcessDemo {

   public static void main(String[] args) {
      try {
         ProcessBuilder pb = new
            ProcessBuilder("cal", "2022");
         final Process p=pb.start();
         BufferedReader br=new BufferedReader(
            new InputStreamReader(
               p.getInputStream()));
               BufferedWriter bw=new BufferedWriter(
                  new FileWriter(new File("mycal2022.txt")));
                  String line;
                  while((line=br.readLine())!=null){
                     bw.write(line);
                  }
                  bw.close();
      } catch (Exception ex) {
         System.out.println(ex);
      }
   }
}

Java does not provide a straightforward way to deal with piped command, such as:

<pre’>echo ‘scale=24; 22/7’ | bc

The pipes, specified as ‘|’, are actually interpreted by the shell as a means of communication between two processes. Java has no clue about how to interpret it. But, we can invoke the shell and send the piped command to it as a command line argument to it. This technique is more of a hack rather than a true support from Java. By the way, there are roundabout and complex ways of doing it to achieve a similar effect, such as by using redirection of the input and output streams. Let’s not delve into those complexities here for now.

Therefore, we can modify the above code a little and run piped command by invoking the shell as follows.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;

public class ProcessDemo {

   public static void main(String[] args) {

      try {
         ProcessBuilder pb = new
            ProcessBuilder("/bin/sh", "-c",
         "echo 'scale=24;22/7' | bc");
         final Process p=pb.start();
         BufferedReader br=new BufferedReader(
            new InputStreamReader(
               p.getInputStream()));
               String line;
               while((line=br.readLine())!=null){
                  System.out.println(line);
               }
      } catch (Exception ex) {
         System.out.println(ex);
      }
   }
}

Conclusion

There is an abstract static class, called ProcessBuilder.Redirect, associated with the ProcessBuilder class. This class represents the link to source and destination I/O of the sub processes. The I/O can be redirected to a file using to() methods, from a file using from() methods, or appended to a file using the appendTo() method. Creating a process is simple: Create an instance of ProcessBuilder with a list of commands and arguments, and invoke the start() method on that instance. That’s all.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories