JVM Advent

The JVM Programming Advent Calendar

Java 9 at a glance

To many Java 9 may seem to be a maintenance release that pushes forward project Jigsaw that couldn’t make it in Java 8. But along with the new module system in the JDK and a number of internal changes associated with it Java 9 brings also a number of cool new stuff to the developer’s toolbox. Here are the highlights:

  • JShell – Java now has a built-in shell;
  • new process API – provides many capabilities for process handling lacking by default in previous releases;
  • putting G1 as the default garbage collector – in Java 8 this was the parallel GC;
  • brand new HTTP/2 client – provided by the jdk.incubator.httpclient.HttpClient class and the classes of the jdk.incubator.httpclient package;
  • new stack walking API – provides a standard API for analyzing and working with Java stack traces;
  • new reactive programming API – provides a standard mechanism in the JDK java.util.concurrent.Flow class;
  • language enhancements – these are a few minor language improvements with the most significant being the ability to have private methods in interfaces (e.g. for use by default methods introduced in Java 8). Earlier proposal for introducing a var keyword to the language was moved for Java 10 (although planned for 9 initially). Further improvements are coming along from project Coin (encompassing a set of small language changes);
  • Misc – improved Javadoc, collection factory methods (such as Set.of(…), Map.of(…), List.of(…)) , stream API improvements, private interface methods, multirelease JARs and a number of others (including concurrency and security enhnancements) which are not covered by the article (the curious reader can review the Java 9-related JEPs here: http://openjdk.java.net/projects/jdk9/).

Another thing that didn’t make it in JDK 9 is packaging of the JMH tool and providing default JMH microbenchmarks for use by applications – work is still ongoing there …

Breaking the monolith

  Let’s consider that we have a pre-Java 9 application that exports text to different formats – text, pdf or word. The application structure is the following:

exporter

The exporter.IExporter interface provides the common interface for different exporters:

public interface IExporter {
	public void export(String text, ByteArrayOutputStream baos);	
}

The three concrete exporter implementations are the following:

public class PdfExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		Document document = null;
		try {
			document = new Document();
			PdfWriter.getInstance(document, baos);
			document.open();
			document.add(new Paragraph(text));
			document.close();
		} catch (DocumentException e) {
			if (document != null) {
				document.close();
			}
		}
	}
}
public class WordExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		XWPFDocument document = null;
		try {
			document = new XWPFDocument();
			XWPFParagraph paragraph = document.createParagraph();
			XWPFRun run = paragraph.createRun();
			run.setText(text);
			document.write(baos);
		} catch (IOException e) {
			try {
				if (document != null) {
					document.close();
				}
			} catch (IOException e1) {
			}
			// some logging or proper error handling ...
		}
	}
}
public class TextExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		try {
			baos.write(text.getBytes());
		} catch (IOException e) {
			// some logging or proper error handling ...
		}
	}
}

The exporter.Main classes is the entrypoint of the application and using only the PdfExporter class if may have the following implementation:

public static void main(String[] args) throws IOException {
		PdfExporter exporter = new PdfExporter();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		exporter.export("sample text", baos);
		FileOutputStream fos = new FileOutputStream("example.pdf");
		fos.write(baos.toByteArray());
		fos.close();
}

 For the PDF and Word exporters we need the following dependencies (we are using Maven for building the application):

<dependencies>
		<dependency>
			<groupid>org.apache.poi</groupid>
			<artifactid>poi</artifactid>
			<version>3.16</version>
		</dependency>
		<dependency>
			<groupid>org.apache.poi</groupid>
			<artifactid>poi-ooxml</artifactid>
			<version>3.16</version>
		</dependency>
		<dependency>
			<groupid>com.lowagie</groupid>
			<artifactid>itext</artifactid>
			<version>2.1.7</version>
		</dependency>		
</dependencies>

So first thing is to define the modules for your application and the modules of the JDK you will be willing to use. Your initial application has all JDK classes on the classpath but you can restrict that in Java 9 only to the JDK modules you need. For our simple application we only uses java.io classes from the JDK which are part of the java.base module that is required by every Java application. No other JDK modules are required for our simple application so we can use a very minimalistic version of the JRE for our application.

For the actual application we can easily derive four logical modules:

  • exporter– this module provides the common API for all exporters (the exporter.IExporter class);
  • exporter.word – this modules provides the Word exporter;
  • exporter.pdf – this modules provides the PDF exporter;
  • exporter.text – this modules provides the text exporter;
  • exporter.demo – this modules contains the Main class and provides the entrypoint of our application.

The next thing is to add the module metadata for each module we defined. For that purpose we define a module-info.java file for each module as follows:

exporter modules

The modules metadata for each module has the following contents (notice how the three concrete exporters depend on the API module and that we don’t require java.base module as it is implicitly required):

module exporter {
	exports exporter;
}
module exporter.pdf {
	requires exporter;
	requires itext;
	exports exporter.pdf;
}
module exporter.text {
	requires exporter;
	exports exporter.text;
}
module exporter.word {
	requires exporter;
}
module exporter.demo {
	requires exporter;
	requires poi.ooxml;
	requires poi.ooxml.schemas;
	exports exporter.word;
}

We still need somehow reference the third party libraries (IText and Apache POI) from the modules. If these libraries were modularized version we could have simply downloaded and required them in the corresponding application modules. However we cannot make this assumption and so we need a way to reference them as dependencies “as they are”. For the purpose of migration the Java platform provides a mechanism to use third-party libraries as Java modules. If these are on the classpath of our application they will be included in a global “unnamed” module and all public classes from the libraries will be visible to our application modules. On the other hand as a new module path is introduced for modular application that specifies all application modules third party libraries can be also referenced from the module path using the name of the JAR file (without the .jar extension) – these are so-called automatic modules. We would prefer the second option as the more “modular” approach towards migration. Now the final thing is to build and run our modularized application. In fact we have different strategies for the module structure: such as multiple modules in the same project (the one we have), one module per project or even nested module structure. Also we can place the module-info.java file anywhere we want in the module as long as specified as part of the javac compilation process (hence in the root of the module directory).

At the time of writing of this article Maven still doesn’t support building of modular applications and also Java IDEs (such as Eclipse, IntelliJ and NetBeans) have partial and experimental support for Jigsaw modules. For that reason we will demonstrate how to build our modularized application from the command line. To do this we will create first the modules directory in the original project root. And build each of the five modules as follows (please take note that Java 9 also provides a –module-source-path parameter for javac so that modules can be compiled at once but we will not use for the purpose of demonstrating how to build modules separately) – we need to be in the root directory of the project and have the latest JDK 9 bin directory on the PATH:

mkdir modules\exporter modules\exporter.pdf modules\exporter.word modules\exporter.text modules\exporter.demo
javac -d modules\exporter src\exporter\module-info.java src\exporter\IExporter.java
javac --module-path modules -d modules\exporter.text src\exporter\text\module-info.java src\exporter\text\TextExporter.java
javac --module-path modules;C:\Users\Martin\.m2\repository\com\lowagie\itext\2.1.7 -d modules\exporter.pdf src\exporter\pdf\module-info.java src\exporter\pdf\PdfExporter.java
javac -cp C:\Users\Martin\.m2\repository\org\apache\poi\poi\3.16\poi-3.16.jar --module-path modules;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml\3.16;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml-schemas\3.16 -d modules\exporter.word src\exporter\word\module-info.java src\exporter\word\WordExporter.java
javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

Two important points to make here. First make sure you exclude the sources jars (if any) from the Maven directories of the third party dependencies as this may confuse the compiler into thinking you have duplicate module jars in the same directories (e.g. poi-3.16 and poi-3.16-sources.jar). Second with automatic modules you may have the same package exported from different libraries (also called split packages) which is not allowed in Java 9. This is case with Apache POI: both the poi and poi-ooxml libraries required by our exporter.word module export the org.apache.poi package which results in compilation error if both of them are included as automatic modules. In order the workaround this problem in our case we include the poi library on the classpath that makes up the so-called global unnamed module that is visible from the automatic modules. Now in order to run the demo application you need to run the following command that combines the module paths/classpaths used for compilation above and points to the Main class as the main entrypoint of our application:

javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

 As a result you should have the example.pdf file generated in the current directory.

Playing around with the Java Shell

The JSHell REPL is located in the bin directory of JDK9 and you can simply run it by navigating to that directory and typing jshell. You can (re-)define and inspect variables, imports, methods and types. You can also save the current progress and load it again in JSHell. There is also awesome autocompletion support for imports, types and methods. To illustrate the basics of JSHell considering trying the following set of commands in JShell:

2 + 3
public int sum(int a, int b) { return a + b; }
import java.util.logging
import java.util.logging.*
Logger x = null;
class z {}
/help
/imports
/vars
/methods
/env
/types
/list
/list -all
/save script.jsh
/open script.jsh
/exit

Taking control of external processes

Before Java 9 you can use two methods to create new processes – either using the Runtime.getRuntime().exec() method or the java.lang.ProcessBuilder class as follows:

Process p = Runtime.getRuntime().exec("cmd /c notepad");
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", “notepad");
Process p = pb.start();</pre>

This however is quite limiting in terms of managing the created processes or inspecting other external processes in the host operating system. For that reason JDK 9 introduces a number of the new methods to the java.lang.Process class on one hand and a new utility provided by the java.lang.ProcessHandle that allows for manipulation of external process in a streamlike manner on the other. Here are a few self-descriptive examples of the new process API:

		LOGGER.info("PID: " + process.pid());
		LOGGER.info("Number of children: " + process.children().count());
		LOGGER.info("Number of descendants: " + process.descendants().count());
		
		ProcessHandle.Info info = process.info();
		LOGGER.info("Process command: " + info.command());
		LOGGER.info("Info: " + info.toString());
		
//		ProcessHandle handle = process.toHandle();
		
		CompletableFuture<process> exitedFuture = process.onExit();
		exitedFuture.whenComplete((p, e) -&gt; { LOGGER.info("Process exited ... ");});
		exitedFuture.get();</process></pre>
<pre class="brush: java;">ProcessHandle[] processes = ProcessHandle.allProcesses().filter((pHandle) -&gt; { return pHandle.info().toString().contains(name); }).toArray(ProcessHandle[] :: new);
		for(ProcessHandle process : processes) {
			LOGGER.info("Process details: " + process.info().toString());
		}
		
		return processes;

Boosting performance with G1

The G1 garbage collector is not new but is introduced in JDK 7. The specific thing about G1 is that works per regions in heap rather than generations which is a more fine-grained approach towards cleaning up garbage. It aims to clean up as much garbage as possible while respecting a set of contraints. It is a low-pause collector which makes a trade off high throughput for low pause times. In JDK 9 it becomes the default garbage collector replacing the parallel GC which is a higher throughput GC. The assumption behind this change is that user experience improvement resulting from lower GC pause times is better that high througput of the collector (which is the case with parallel GC). Whether that assumption is true or not is something left for applications to decide – if someone is not willing to put the risk of moving to G1 can still specify the parallel GC for the JVM with -XX:-UseParallelGC

Getting ready for HTTP 2.0

A new HTTP 2.0 client is provided by the jdk.incubator.http.HttpClient class which is still in incubation phase (meaning more changes might come along).

Walking the stack trace

A new stack trace inspection API is provided by the java.lang.StackWalker class. It can be used to filter out and manipulated stack trace information in a fine-grained manner using streamlike operations.

Encompassing reactive programming

Applications that want to provide a publish-subscribe mechanism fitting the reactive programming paradigm must provide an implementation of the java.util.concurrent.Flow class. One standard publisher provided by the JDK that implements the Flow.Publisher interface is provided by the java.util.concurrent.SubmissionPublisher class.

Author: Martin Toshev

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén