Custom OpenJDK 9 Builds on Ubuntu 16.04

November 21, 2017

If, for some reason, you need to build OpenJDK 9 yourself, here is how to do it on Ubuntu 16.04.

All commands below are executed in this order on a freshly launched (LXD) Ubuntu 16.04 image.

First, we need mercurial (hg).

$ sudo apt-get update
$ sudo apt-get install mercurial

Now, lets clone the JDK 9 repo from the OpenJDK update releases project.

$ hg clone http://hg.openjdk.java.net/jdk-updates/jdk9u

This ends very quick because this repo is like a super/meta repo, now we need to clone the actual source code. This takes around 10mins.

$ cd jdk9u
$ bash ./get_source.sh

Hint: If you are inside LXD container, last command (and actually common/bin/hgforest.sh which it calls) fails. You can set HGFOREST_REDIRECT environment variable to a file (e.g. export HGFOREST_REDIRECT=get_source.log) before running this, so it outputs log to this file rather than /dev/stdout which fails inside the LXD container.

In order to build the JDK 9, you need three things:

  • The native compiler collection (e.g. gcc) and build tools (make etc.)
  • The previous release of JDK (so 8). The reason for this is parts of OpenJDK is written in Java, so you need a Java Compiler. This is called Boot JDK.
  • External dependencies, tools and libraries etc.

Lets install the gcc compiler collection and OpenJDK 8.

$ sudo apt-get install build-essential openjdk-8-jdk

This is like 100MB download.

Now the required libraries and tools (FreeType2, CUPS, X11, ALSA, libffi, libelf, unzip, zip):

$ sudo apt-get install libfreetype6-dev libcups2-dev libx11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev libasound2-dev libffi-dev libelf-dev unzip zip

Note: You also need llvm to compile zeroshark variant. However, I think it requires llvm 3.4 which is not available in default Ubuntu repos anymore. Unfortunately it is not very straightforward to build zeroshark variant. Also, Shark compiler (so I guess zeroshark variant also) will probably be removed from OpenJDK in the future.

There are some other dependencies but they are bundled with OpenJDK sources (such as libjpeg under jdk/src/java.desktop/share/native).

Now we are ready to build. As usual, we first run the configure script, it creates the build config files, then we issue make. make help gives a summary of make targets, some of them are:

make [default] # Compile all modules in langtools, hotspot, jdk
               # jaxws, jaxp and corba, and create a runnable
               # "exploded" image
make images # Create complete jdk and jre images (alias
            # for product-images)
make clean  # Remove all files generated by make, but not
            # those generated by configure
make dist-clean # Remove all files, including configuration

Now lets run configure.

Hint: If you will do such builds many time, you can install ccache package and use --enable-cache every time you run configure. This will speed up the builds.

$ bash configure

This should end without any error. If you see any error, either you did something wrong or I wrote something wrong in the above steps.

Lets look at the last lines of the configure output:

checking JVM features for JVM variant ‘server’… all-gcs aot cds compiler1 compiler2 fprof graal jni-check jvmci jvmti management nmt services vm-structs
configure: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/configure-support/config.status
config.status: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/spec.gmk
config.status: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/bootcycle-spec.gmk
config.status: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/buildjdk-spec.gmk
config.status: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/compare.sh
config.status: creating /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release/Makefile
====================================================
A new configuration has been successfully created in /home/ubuntu/jdk9u/build/linux-x86_64-normal-server-release
using default settings.
Configuration summary:
* Debug level: release
* HS debug level: product
* JDK variant: normal
* JVM variants: server
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64
* Version string: 9.0.1-internal+0-adhoc.ubuntu.jdk9u (9.0.1-internal)
Tools summary:
* Boot JDK: openjdk version "1.8.0_151" OpenJDK Runtime Environment (build 1.8.0_151–8u151-b12–0ubuntu0.16.04.2-b12) OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode) (at /usr/lib/jvm/java-8-openjdk-amd64)
* Toolchain: gcc (GNU Compiler Collection)
* C Compiler: Version 5.4.0 (at /usr/bin/gcc)
* C++ Compiler: Version 5.4.0 (at /usr/bin/g++)
Build performance summary:
* Cores to use: 8
* Memory limit: 31933 MB

Starting from the bottom:

  • Build performance summary will be different for you, this shows it will use 8 cores and maximum 32GB memory on my computer (which is what the computer has).
  • Tools summary shows it will use OpenJDK 8 as Boot JDK, and gcc compiler collection version 5.4.0 to build the OpenJDK 9.
  • Configuration summary shows we are building a release version, normal JDK variant, server JVM variant, for linux x86 64-bit.

The first line in the output above is important, it shows:

checking JVM features for JVM variant ‘server’… all-gcs aot cds compiler1 compiler2 fprof graal jni-check jvmci jvmti management nmt services vm-structs

So the “server” variant of JVM to be built will include these features. It is what the variant “server” contains by default. Looking at generated build/linux-x86_64-normal-server-release/spec.gmk file we can see:

## Which JVM variants to build (space-separated list)
JVM_VARIANTS := server
JVM_VARIANT_MAIN := server
## Lists of features per variant. Only relevant for the variants listed in
## JVM_VARIANTS.
JVM_FEATURES_server := all-gcs aot cds compiler1 compiler2 fprof graal jni-check jvmci jvmti management nmt services vm-structs
JVM_FEATURES_client := all-gcs cds compiler1 fprof jni-check jvmci jvmti management nmt services vm-structs
JVM_FEATURES_core := all-gcs cds fprof jni-check jvmti management nmt services vm-structs
JVM_FEATURES_minimal := compiler1 minimal
JVM_FEATURES_zero := all-gcs cds fprof jni-check jvmti management nmt services vm-structs zero
JVM_FEATURES_zeroshark := all-gcs cds fprof jni-check jvmti management nmt services shark vm-structs zero
JVM_FEATURES_custom :=
## Used for make-time verifications
VALID_JVM_FEATURES := compiler1 compiler2 zero shark minimal dtrace jvmti jvmci graal fprof vm-structs jni-check services management all-gcs nmt cds static-build link-time-opt aot
VALID_JVM_VARIANTS := server client minimal core zero zeroshark custom

So possible variants are: server, client, minimal, core, zero, zeroshark and custom. What features they contain is also specified here. As you guess, normally, everybody uses either server or client variant depending on the computer.

Note: Actually you cannot build the client variant easily, because the client variant is not supported in 64-bit targets anymore. So, if you try to build, you will get errors. More info and a possible workaround is here.

You can check which JVM you have:

$ java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151–8u151-b12–0ubuntu0.16.04.2-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)

So the OpenJDK 8 I have installed is a server variant in mixed mode (interpret + JIT compile).

The meaning of other variants are:

  • minimal: a minimal build with compiler1
  • core: it seems like intepreter only variant with some features
  • zero: Zero assembly code, no JIT no AOT interpreter
  • zeroshark: Zero assembly code but with zero-assembly Shark JIT compiler
  • custom: build with no default features, you can select individual features

The features roughly mean:

  • all-gcs: all garbage collectors otherwise only Serial GC is available
  • aot: Ahead-Of-Time (AOT) compilation support
  • cds: enables Class Data Sharing with -Xshare option
  • compiler 1: JIT Compiler 1
  • compiler 2: JIT Compiler 2 (used only in server variant)
  • dtrace: enables dtrace support
  • fprof: enables Flat Profiler, to support the JVM -Xprof option
  • graal: exposes JVM functionality as API
  • jni-check: enables additional JNI checks support with -Xcheck:jni option
  • jvmci: JVM Compiler Interface (used by dynamic compilers like graal)
  • jvmti: JVM Tool Interface
  • link-time-opt: link time optimizations
  • management: enables Java Management features
  • minimal: it seems like only setting the VM Type to Minimal
  • nmt: native memory tracking
  • services: enables more serviceability features, more info here
  • shark: LLVM based JIT Compiler for zero
  • static-build: makes a static build as the name suggests :)
  • vm-structs: used by HotSpot Serviceability Agent (SA), more info here
  • zero: Zero-Assembler port of JDK

Now lets build the server variant.

$ make

This took a little longer than 4 minutes (on Intel Xeon E3 1245 v5 4-cores at 3.50 Ghz, 32GB memory, NVMe SSD).

Under the build/linux-x86_64-normal-server-release/images we find the jdk and jre folders.

$ ./build/linux-x86_64-normal-server-release/jdk/bin/java -version
openjdk version "9.0.1-internal"
OpenJDK Runtime Environment (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u)
OpenJDK 64-Bit Server VM (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u, mixed mode)

Everything is as expected, this is a server VM in mixed mode.

Now lets build the zero variant.

$ make dist-clean
$ bash configure --with-jvm-variants=zero
$ make

This took ~13 minutes.

$ ./build/linux-x86_64-normal-zero-release/jdk/bin/java --version
openjdk 9.0.1-internal
OpenJDK Runtime Environment (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u)
OpenJDK 64-Bit Zero VM (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u, interpreted mode)

As expected, this is a pure interpreted mode JVM.

If you are very careful, you may see dtrace feature is not included in above builds. The reason is its configuration is set auto as default, meaning if the dependencies are met, then it is built into. However, we do not have the dependency so it is not built. We need:

$ sudo apt-get install systemtap-sdt-dev

Now lets run configure again.

$ make dist-clean
$ bash configure

Looking at the same line in configure output:

checking JVM features for JVM variant 'server'... all-gcs aot cds compiler1 compiler2 dtrace fprof graal jni-check jvmci jvmti management nmt services vm-structs

Now dtrace feature is included as well.

As the last example, we can try the minimum possible build.

$ make dist-clean
$ bash configure --with-jvm-variants=custom --enable-dtrace=no --with-jvm-features=minimal

Instead of using minimal variant, I choose custom variant and then include the minimal feature, because minimal variant also includes (JIT) compiler1 which I do not want. As far as I understand enabling the minimal feature does nothing other than defining the VM Type as Minimal (not Server, Client, Zero or Shark) to be shown in the version output. I also disabled dtrace support.

Looking at the configure output:

checking JVM features for JVM variant 'custom'... minimal

Includes only the minimal feature. Lets build it:

$ make

Lets see the version:

$ ./build/linux-x86_64-normal-custom-release/jdk/bin/java -version
openjdk version "9.0.1-internal"
OpenJDK Runtime Environment (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u)
OpenJDK 64-Bit Minimal VM (build 9.0.1-internal+0-adhoc.ubuntu.jdk9u, interpreted mode)

It is reported as Minimal VM and interpreted mode only.

Lets check some features:

$ ./build/linux-x86_64-normal-custom-release/jdk/bin/java -javaagent:a -version
Instrumentation agents are not supported in this VM
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
$ ./build/linux-x86_64-normal-custom-release/images/jdk/bin/java -Xprof -version
Flat profiling is not supported in this VM.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

As you see, because -javaagent and -Xprof options depend on the features that we did not include in this build, they are reported as being not supported.

As a last note, you may wonder how -client and -server options work and it is actually pretty simple. You can see there is a file called jvm.cfg in the lib folder for JDK. This file contains the configuration about which variants are supported. For example, for the openjdk-8-jdk on Ubuntu, the file contains this:

/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/jvm.cfg
-server KNOWN
-client IGNORE
-zero KNOWN
-dcevm KNOWN

It means, it supports -server, -zero and -dcevm variants and -client is recognized but ignored and default (the first one, so -server) is used. We can see this also with:

$ java -help
...
-server   to select the "server" VM
-zero   to select the "zero" VM
-dcevm   to select the "dcevm" VM
...

However, if you run:

$ java -zero
Error: missing `zero' JVM at `/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/zero/libjvm.so'.
Please install or use the JRE or JDK that contains these missing components.

you get an error. This also gives hint at how different JVMs are supported in single installation. There is a folder with the same name (zero) and libjvm.so under that folder is used. Because I only have the server folder, only -server works. I did not know this before, so if we search the Ubuntu repo:

$ apt-cache search jdk | grep zero
openjdk-8-jre-zero - Alternative JVM for OpenJDK, using Zero/Shark

we see there is also zero variant available. If you install this, you can use -zero option as well.