Java9 Modularity (Part1)
Modularity is a major feature in Java9 release that was developed under project jigsaw. Until Java9, all previous JDK releases were monolithic — code was tightly coupled. Any application that does not use AWT/Swing APIs still required the entire JRE, resulting in a larger memory footprint. Compact profiles were introduced in Java8 which provided three different compact profiles with varying memory footprint.
Java9 introduced the modular system that allows us overcome these issues by:
· Organizing the JDK classes and data in loosely coupled modules addressing the scalability issues
· Encapsulating JDK internal APIs and thereby improving Security
· Declaring dependencies clearly and exporting the packages for use by other modules
· Providing a link time feature to link the required modules to produce a Custom runtime JRE for various domains like mobile, IOT, cloud and thereby reducing startup time and system footprint
What is a Module?
Module = Code + data
code — java packages with classes & native code
data — configuration file, resources
A Module has a name, declares dependencies on other modules, exports packages that contain the public types that can be accessible outside this module and specifies the services it uses or the service implementations it provides. All these are specified in a module descriptor file named module-info.java, that is included in the root directory of the module
A module (Module A) can access public types in another module (Module B) only if, Module B exports the packages containing the public types to all modules or at least Module A (this is known as qualified exports) and the Module A declares a dependency on Module B
Unlike previous releases, public no longer means public. A public type can only be accessible if the module exports the package containing the public type
For example, here is the module descriptor of a module named A, indicating the following:
- module A is dependent on module B
- module A exports package x.y to all other modules
- module A exports package u.v to only module C (Qualified exports)
module A {
requires B;
exports x.y;
exports u.v to C;
}
JDK itself is divided into modules, that can be combined and linked. Linked ?? — yes, there is a new link time introduced between javac and java, more on this later. Modules in the JDK were re-organized as below:
Before Java9
src/{share,$OS}/{classes,native}/$PACKAGE/*.{java,c,h,cpp,hpp}
In Java9
src/$MODULE/{share,$OS}/classes/$PACKAGE/*.java
native/include/*.{h,hpp}
$LIBRARY/*.{c,cpp}
conf/*
You can see JEP 200 to understand how the JDK itself was divided into modules and view the Module graph of the JDK
To list the modules in the JDK, we can use java –list-modules. Here is a sample output:
java.activation@9-ea
java.annotations.common@9-ea
java.base@9-ea
java.compact1@9-ea….
Time for some coding :)
1)Here is a simple module, com.greetings
// Greetings.java:package com.greetings;
public class Greetings {
public static void main(String[] args){
System.out.println(“Greetings!”); }
}// module-info.java:module com.greetings{}// Note - com.greetings is just dependent on only java.base
// which would be implicitly added
Module name can be anything, but reversed domain name is recommended
Module directory listing:com.greetings/com/greetings/Greetings.java
com.greetings/module-info.java
(Note — module-info.java should be placed directly under the module directory)
Compiling and executing the module:
>> javac -d mods com.greetings/com/greetings/Greetings.java com.greetings/module-info.java>> java -mp mods -m com.greetings/com.greetings.Greetings
Where -mp is the short form of — module-path
2) Let us now look at an example where one module is dependent on another
// Greetings.java:package com.greetings;
public class Greetings {
public static void main(String[] args){
String name = com.hello.Hello.name();
System.out.println(“Greetings “ + name);
}
}module-info.java:module com.greetings{
requires com.hello;
}
com.hello Module
com/hello/Hello.java:package com.hello;
public class Hello {
public static String name() {
return “Runtimes”;
}
}module-info.java:module com.hello{
exports com.hello; // here com.hello is a package}Module directory listing:com.greetings/com/greetings/Greetings.java
com.greetings/module-info.java
com.hello/com/hello/Hello.java
com.hello/module-info.java
Compiling and Executing the module:
>> javac -d mods com.hello/com/hello/Hello.java com.hello/module-info.java>> java -mp mods -m com.greetings/com.greetings.Greetings>> javac -d mods -mp mods com.greetings/com/greetings/Greetings.java com.greetings/module-info.java>> java -mp mods -m com.greetings/com.greetings.Greetings
3) Implied Readability
Let us say Module A requires Module B and Module B requires Module C and Module A invokes a method from Module B that returns an instance of a class in Module C
// module-info.java
module ModuleA {
requires ModuleB;}// ClassA.java in ModuleAClassB b = new ClassB();
ClassC c = b.methodB();// module-info.javamodule ModuleB{requires ModuleC;exports com.pkgB;}// ClassB.javapublic ModuleC methodB(){ClassC c= new ModuleC();…return c;}
Here ModuleA does not declare any dependency on ModuleC but since it uses a method from ModuleB which returns an instance of a class in ModuleC it needs to declare a dependency on ModuleC. To overcome this issue, ModuleB can add requires public on ModuleC. This would allow any other Modules that are dependent on ModuleB to implicitly read ModuleC.
// module-info.javamodule ModuleB{requires public ModuleC;exports com.pkgB;}
Packaging Modules
So far, we described a module as an exploded directory. Now, let us look at packaging a module as a modular jar or a jmod file.
Jmod, is a new format introduced in Java9 to package modules. It differs from jar format as it can include code and data in the modules in a more structured way with separate sub-folders for native code, classes, bin, and resources. It is only used for packaging and cannot be used for invoking an application directly from it, unlike a jar.
Modular Jar, is similar to a regular jar but contains module-info.class at the root level. We can create a modular jar of the above modules using the below commands:
>> mkdir mlib
>> jar --create --file=mlib/com.hello.jar -C mods/com.hello .
>> jar --create --file=mlib/com.greetings.jar --main-class=com.greetings.Main -C mods/com.greetings .
>> java -mp mlib -m com.greetings
We can create a jmod of the modules above using the jmod utility
>> mkdir jmods
>> jmod create --class-path mods/com.hello jmods/hello.jmod
>> jmod create --class-path mods/com.greetings jmods/greetings.jmod
Note: JDK modules are available in jmod format in <JAVA_HOME>/jmods directory
Linking Modules
How do I create my own custom runtime image?
Java9 provides a tool jlink to link the required modules and produce a custom runtime image. Below are the commands to create a custom runtime image with the greetings module listed above.
>> jlink --module-path %JAVA_HOME%/jmods;./jmods --add-modules com.greeting --output greetingsapp>> greetingsapp\bin\java -m com.greetings/com.greetings.Main>> greetingsapp\bin\java --list-modules
com.greetings
com.hello
java.base@9-ea
Though the above example specifies the application module in jmod format, jlink accepts application modules in a modular jar or exploded directory too.
The linked modules are stored in <JAVA_HOME>/lib/modules file which is a jimage format. Size of this file in the custom runtime image produced will be much lesser than compared to the one in the JDK as it contains only one module java.base from the JDK along with the application modules.
In the next part,we will look at compatibility issues in Java9 and migrating to Java9.
References