Friday, August 26, 2022

Quiz yourself: Controlling the number of instances of a class

Which is the best approach: singletons, sealed classes, abstract classes, final classes, or enums?

Imagine that your design requires exactly four instances of a particular class.

Quiz Yourself, Oracle Java, Java Tutorial and Materials, Java Certification, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation

Which approach best provides for this need? Choose one.

A. Singletons

B. Sealed classes

C. Abstract classes

D. Final classes

E. Enums

Answer. Option A is incorrect because singleton is not a Java keyword, nor is it a concept defined in the Java Language Specification. Singleton is actually the name of a design pattern and as a design pattern, it can be implemented in several different ways.

From the perspective of this question, the most important characteristic of a singleton is that it limits the number of instances of its type to one per JVM. The quiz question simply requires that the number of instances be fixed; it does not require that number to be one, and as such, an approach is necessary that allows the instance count to be greater than one. Consequently, option A is incorrect.

Option B is also incorrect. Sealed classes were added in Java 17, and the feature’s goal is to fix a set of types that exist as specializations of a particular type. For example, you could specify that a class RoadTransport is permitted to have only subtypes Car, Truck, Bus, Motorcycle, and Bicycle. Note that the sealed classes feature does not limit the number of instances of these classes that can exist.

In addition, option C is incorrect. Abstract classes are a mechanism to capture the common functionality that’s shared by several types in a single class, and they require developers to add specific additional functionality to subclasses. As a side note, an abstract class can be sealed, but as before, this does not have an impact on the number of instances you can create.

Option D is also incorrect. The final modifier, when applied to a class, means you cannot subclass that class. However, the modifier does not imply any control on the number of instances.

Option E is correct, because an enum type provides a clear, language-supported solution to control the number of instances of a class. There are some limitations on enum types; notably, the parent class of an enum is always java.lang.Enum, though an enum can implement interfaces of your choosing.

A second limitation is that an enum is always a final class and cannot be subclassed. This is important, since instances of a subclass are also instances of their parents, and if subclassing were possible, those subclasses would break the contract of a fixed number of instances.

It’s worth mentioning that a programmer can tightly control the construction of any class, and with it the number of instances of such a class, by marking all the constructors of that class as private and providing access to instances through a factory method. In fact, this idea is essential to the implementation of both Java’s enum type and the Singleton design pattern. However, because this approach is not an option for this question, it’s not a correct answer here.

Conclusion. The correct answer is option E.

Source: oracle.com

Wednesday, August 24, 2022

The arrival of Java 17!

Oracle is proud to announce the general availabilty of JDK 17. This release is the eighth Feature Release delivered on time through the six-month release cadence. This level of predictability allows developers to easily manage their adoption of innovation thanks to s steady stream of expected changes.

Java 17, Oracle Java Exam Prep, Oracle Java Prep, Oracle Java Certification, Oracle Java Career, Java Skills, Java Jobs, Java Preparation Exam

Java’s ability to boost performance, stability, and security continues to make it the world’s most popular programming language. According to an IDC report over ten million developers, representing 75% of full-time developers worldwide, use Java, more than any other language.

JDK 17 is now available!


Oracle now offers JDK 17 for developers, end-users, and enterprises. As an LTS release, Oracle JDK 17 will receive performance, stability and security updates for at least 8 years following the Oracle Critical Patch Update (CPU) schedule as outlined in the Oracle Java SE Support Roadmap.

Oracle JDK 18, the next six month cadence release is now scheduled for in March 2022.

Java 17 is the second long-term support (LTS) under the release cadence announced in 2018. Oracle has announced plans to shorten the time between future LTS releases, from 3 years to 2 years so you should expect the next LTS to be Java 21 in September of 2023.

Another important change with Oracle JDK 17 is new simple license terms which will allow companies to use Oracle JDK 17, including the quarterly performance, stability, and security patches, at no cost for at least the next three years, allowing one full year of overlap with the next LTS release. Java SE Subscribers get access to Oracle’s Java SE Support and commercial features such as GraalVM Enterprise, Java Management Service and the Advanced Management Console.

Java 17, Together


As with previous release, with Java 17, we continue to celebrate the contributions from many individuals and organizations in the OpenJDK Community — we all build Java, together!

JDK 17 Fix Ratio


The rate of change over time in the JDK releases has remained largely constant for years, but under the six-month cadence the pace at which production-ready features and improvements are delivered has vastly improved.

Instead of making tens of thousands of fixes and delivering close to one hundred JEPs (JDK Enhancement Proposals) every few years as we did with yesteryear Major Releases, enhancements are delivered in leaner Feature Releases on a more manageable, predictable six-month schedule. The changes range from significant new features to small enhancements to routine maintenance, bug fixes, and documentation improvements. Each change is represented in a single commit for a single issue in the JDK Bug System.

Of the 12,733 JIRA issues marked as fixed in Java 12 through Java 17 at the time of their GA, 9,218 were completed by people working for Oracle while 3,515 were contributed by individual developers and developers working for other organizations.

In Java 17, of the 2,645 JIRA issues marked as fixed, 1,774 were completed by Oracle, while 871 were contributed by other members of the Java community. Going through the issues and collating the organization data from assignees results in the following chart of organizations sponsoring the development of contributions in Java 17:

Java 17, Oracle Java Exam Prep, Oracle Java Prep, Oracle Java Certification, Oracle Java Career, Java Skills, Java Jobs, Java Preparation Exam

Oracle would like to thank the developers working for organizations like Amazon, NTT Data, Red Hat, SAP and Tencent for their notable contributions.  We are also thankful to see contributions from smaller organizations such as Bellsoft, DataDog, Loongson, Skymatic and independent developers who collectively contributed 6% of the fixes in Java 17.

We are equally grateful to the many experienced developers who reviewed proposed changes, the early adopters who tried out early access builds and reported issues, and the dedicated professionals who provided feedback on the OpenJDK mailing lists. 

The following individuals provided invaluable feedback on build quality, logged good quality bugs, or offered frequent updates:

◉ Jaikiran Pai (Apache Ant)
◉ Rick Hillegas (Apache Derby)
◉ Uwe Schindler (Apache Lucene)
◉ Mark Thomas (Apache Tomcat)
◉ Martin Grigorov (Apache Tomcat, Apache Wicket)
◉ Rafael Winterhalter (Byte Buddy)
◉ Yoann Rodière (Hibernate ORM, Validator, Search, Reactive)
◉ Marc Hoffman (JaCoCo)
◉ Lukas Eder (jOOQ)
◉ Christian Stein (JUnit 5)
◉ David Karnok (RxJava)

Additionally, through the Quality Outreach program we would like to thank the following FOSS projects and individuals who provided excellent feedback on testing Java 17 early access builds to help improve the quality of the release.

◉ Apache Aries Spi Fly
◉ Apache CXF
◉ Apache Zookeeper (Enrico Olivelli)
◉ Aries JAX-RS
◉ BurningWave
◉ DataSketches
◉ Eclipse Collections
◉ Eo-yaml
◉ FXGL
◉ JabRef
◉ JaCoCo (Evgeny Mandikov)
◉ Java Katas (Chandra Guntur)
◉ Jenkins
◉ Jobrunr
◉ JOOQ
◉ JUnit
◉ Karate
◉ MyBatis (Iwao Ave)
◉ Netty
◉ PDFsam
◉ Sedja
◉ Selenide
◉ Syncope
◉ Vaadin

New in Java 17


Along with thousands of performance, stability and security updates, Java 17 delivers fourteen enhancements/changes (known as JDK Enhancement Proposals - JEPs), including three delivered in incubator modules and one preview language feature.

Incubator modules allow putting non-final APIs and non-final tools in the hands of developers and users to gather feedback that will ultimately improve the quality of the Java platform.

Similarly, Preview Features are fully specified and fully implemented Language or VM Features of the Java SE Platform; and yet impermanent. They are made available in JDK Feature Releases to allow for developer feedback based on real-world uses, before them becoming permanent in a future release.  This also affords tool vendors the opportunity to work towards supporting features before they are finalized into the Java SE Standard.

We’ve grouped the fourteen JEPs delivered with Java 17 into seven categories:

1. Language Feature

JEP 409: Sealed Classes

Sealed Classes allow API designers to specify which classes or interfaces may extend or implement a given class. Having an exhaustive list of cases to consider when modeling a problem can simplify development. JEP 409 was developed in the OpenJDK Project Amber, which aims to continually improve developer productivity through evolution of the Java programming language.

2. Updates and Improvements on Core Libraries

JEP 306: Restore Always-Strict Floating-Point Semantics

The Java programming language and Java virtual machine originally only had strict floating-point semantics. Starting in JDK 1.2, small variances in those strict semantics were allowed by default to accommodate limitations of then-current hardware architectures. Those variances are no longer helpful or necessary and have been removed by JEP 306.

JEP 356: Enhanced Pseudo-Random Number Generator

Updates to java.util.random improve the interoperability of different PRNGs (Pseudo-Random Number Generators) and make it easy to request an algorithm based on requirements rather than hard coding a specific implementation.  Changes include new interface types and implementations for pseudorandom number generators (PRNGs), including jumpable PRNGs and an additional class of splitable PRNG algorithms (LXM) and a new RandomGeneratorFactory class.

JEP 382: New macOS Rendering Pipeline

This new pipeline reduces the JDK’s dependency on the deprecated Apple OpenGL API by implementing a Java 2D rendering pipeline for macOS using the new Apple Metal API.

JEP 415: Context-Specific Deserialization Filters

Filter Incoming Serialization Data, added with JDK 9 (JEP 290), is improved by allowing applications to configure context-specific and dynamically-selected deserialization filters via a JVM-wide filter factory that is invoked to select a filter for each individual deserialization operation. This makes it possible to take advantage of deserialization filters without requiring every stream’s creator to update their code or making the filter too restrictive or too permissive.

3. New Platform Support

JEP 391: macOS AArch 64 Port

Delivers a version of the JDK for macOS that runs natively on Apple’s newer Arm 64 based systems.

4. Previews and Incubators

JEP 406: Pattern Matching for switch (Preview)

Enhances the Java programming language allowing pattern matching to be tested within a switch statement or switch expression. Using pattern matching in switch complex data-oriented queries can be expressed concisely and safely. JEP 406 was developed in the OpenJDK Project Amber.

JEP 412: Foreign Function and Memory API (Incubator)

Improves APIs introduced with JDK 14 and JDK 15 through which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. JEP 412 was developed in the OpenJDK Project Panama which aims to simplify the interaction between Java code and foreign (non-Java) APIs.

JEP 414: Vector API (Second Incubator)

Enhances APIs that allow expressing vector computations in a way that will reliably compile at runtime to optimal vector instructions on supported CPU architectures. Vector operations can deliver performance superior to equivalent scalar computations and are quite common in fields such as Machine Learning, Artificial Intelligence, and Cryptography. JEP 412 was developed in the OpenJDK Project Panama.

5. Future Proofing Java Programs

JEP 403: Strongly Encapsulate JDK Internals

It will no longer be possible to relax the strong encapsulation of internal elements through a single command-line option, as was possible in JDK 9 through JDK 16 This change hides by default all but a few critical internal APIs such as sun.misc.Unsafe. It will still be possible to access existing internal APIs, but this will now require enumerating, as command-line parameters or JAR-file manifest attributes, each package on which encapsulation should be relaxed. The change will lead to more secure applications and less dependencies on non-standard internal implementations.

JEP 403 is the continuation of JEP 396 in JDK 16, which transitioned the JDK from a default of relaxed strong encapsulation to a default of strong encapsulation.

6. Deprecations and Removals

JEP 411: Deprecate the Security Manager for Removal

The Security Manager dates from Java 1.0. It has not been the primary means of securing client-side Java code for many years, and it has rarely been used to secure server-side code.

JEP 398: Deprecate the Applet API for Removal

The Applet API has become essentially irrelevant since all web-browser vendors have either removed support for Java browser plug-ins or announced plans to do so. The Applet API was previously deprecated (though not for removal) in Java 9 (JEP 289) in September 2017.

JEP 407: Remove RMI Activation

Remote Method Invocation (RMI) Activation mechanism has been removed.  This change does not affect the rest of RMI. The RMI Activation mechanism was deprecated for removal in JDK 15 in September 2020.

7. For OpenJDK Contributors

JEP 410: Remove the Experimental AOT and JIT Compiler

The experimental Java-based ahead-of-time (AOT) and just-in-time (JIT) compiler saw little use since its introduction in JDK 9, more widely supported alternatives have emerged, and the effort required to maintain them is significant.  As optional components, they were already removed from JDK 16. This JEP removes the source code from the OpenJDK project.
 

Tooling Support


Timely support for new features by tools and libraries helps drive developer productivity.  With Java 17, we continue to welcome the efforts of leading IDE vendors whose most timely updates offer developers support for current Java versions.  Developers can expect to take advantage of Java 17 support today with the following IDEs:

◉ JetBrains IDEA
◉ Eclipse via a separate marketplace solution

Source: oracle.com

Monday, August 22, 2022

Quiz yourself: Creating records within a Java sealed-type hierarchy

Oracle Java, Java Career, Java Skills, Java Jobs, Java Tutorial and Material, Java Career, Java Certification, Java Prep, Java Preparation

A record type is always implicitly final, so it cannot be extended by either a regular class or a record.

You’re designing an IoT device controlled by Java. The device has two modes, one for day and one for night. The modes differ in terms of configuration data, and that data should be constant, so you have decided to use Java records for the implementation. Further, since no other mode types are permitted, you have decided to seal the mode hierarchy something like this, but have not decided what the type of should be used to implement the parent of the sealed Mode hierarchy:

Read More: 1Z0-829: Oracle Java SE 17 Developer

public sealed <type> Mode permits DayModeRecord, NightModeRecord {

}

Which statement is true? Choose one.

A. The Mode type must be a class.

B. The Mode type must be a record.

C. The Mode type must be an interface.

D. The Mode may be either a class or an interface.

Answer. Generally, a sealed-type hierarchy can have a class or an interface as its root. The remainder of the hierarchy can contain classes or interfaces, provided all leaf nodes of the hierarchy are either final concrete classes or are non-sealed.

If a leaf element is non-sealed, it can be either a class or an interface.

However, a record is prohibited from using an extends clause, because all record types have java.lang.Record as their parent, and this cannot be made explicit. Consequently, a record cannot inherit from any user-selected class. Therefore, making Mode a class would prevent the two mode records from inheriting from the base. From this, you know that the Mode type must not be a class; therefore, option A and option D are incorrect.

Option B suggests that the Mode type should be a record. However, a record type is always implicitly final, so it cannot be extended by either a regular class or a record. Because of this, option B must be incorrect.

You’ve seen that an interface is permitted for the root (and potentially some further elements) in a sealed-type hierarchy, and although a record is prohibited from explicitly extending anything, records are permitted to implement interfaces. Option C proposes making the Mode type an interface, and it’s clearly not only a valid approach, but it’s the only valid way to create a sealed-type hierarchy where a leaf node in the sealed hierarchy is a record. From this, you can see that option C is correct.

Conclusion. The correct answer is option C.

Source: oracle.com

Friday, August 19, 2022

Declarative and Immutable Pipeline of Transformations

A few months ago I made a small Java library, which is worth explaining since the design of its classes and interfaces is pretty unusual. It’s very much object-oriented for a pretty imperative task: building a pipeline of document transformations. The goal was to do this in a declarative and immutable way, and in Java. Well, as much as it’s possible.

Oacle Java, Core Java, Oracle Java Certification, Oracle Java Guides, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Java Jobs, Java Skill

Let’s say you have a document, and you have a collection of transformations, each of which will do something with the document. Each transformation, for example, is a small piece of Java code. You want to build a list of transformations and then pass a document through this list.

First, I made an interface Shift (instead of the frequently used and boring “transformation”):

interface Shift {

  Document apply(Document doc);

}

Then I made an interface Train (this is the name I made up for the collection of transformations) and its default implementation:

interface Train {

  Train with(Shift shift);

  Iterator<Shift> iterator();

}

class TrDefault implements Train {

  private final Iterable<Shift> list;

  @Override

  Train with(Shift shift) {

    final Collection<Shift> items = new LinkedList<>();

    for (final Shift item : this.list) {

        items.add(item);

    }

    items.add(shift);

    return new TrDefault(items);

  }

  @Override

  public Iterator<Shift> iterator() {

      return this.list.iterator();

  }

}

Ah, I forgot to tell you. I’m a big fan of immutable objects. That’s why the Train doesn’t have a method add, but instead has with. The difference is that add modifies the object, while with makes a new one.

Now, I can build a train of shifts with TrDefault, a simple default implementation of Train, assuming ShiftA and ShiftB are already implemented:

Train train = new TrDefault()

  .with(new ShiftA())

  .with(new ShiftB());

Then I created an Xsline class (it’s “XSL” + “pipeline”, since in my case I’m managing XML documents and transform them using XSL stylesheets). An instance of this class encapsulates an instance of Train and then passes a document through all its transformations:

Document input = ...;

Document output = new Xsline(train).pass(input);

So far so good.

Now, I want all my transformations to log themselves. I created StLogged, a decorator of Shift, which encapsulates the original Shift, decorates its method apply, and prints a message to the console when the transformation is completed:

class StLogged implements Shift {

  private final Shift origin;

  @Override

  Document apply(Document before) {

    Document after = origin.apply(before);

    System.out.println("Transformation completed!");

    return after;

  }

}

Now, I have to do this:

Train train = new TrDefault()

  .with(new StLogged(new ShiftA()))

  .with(new StLogged(new ShiftB()));

Looks like a duplication of new StLogged(, especially with a collection of a few dozen shifts. To get rid of this duplication I created a decorator for Train, which on the fly decorates shifts that it encapsulates, using StLogged:

Train train = new TrLogged(new TrDefault())

  .with(new ShiftA()))

  .with(new ShiftB());

In my case, all shifts are doing XSL transformations, taking XSL stylesheets from files available in classpath. That’s why the code looks like this:

Train train = new TrLogged(new TrDefault())

  .with(new StXSL("stylesheet-a.xsl")))

  .with(new StXSL("stylesheet-b.xsl")));

There is an obvious duplication of new StXSL(...), but I can’t simply get rid of it, since the method with expects an instance of Shift, not a String. To solve this, I made the Train generic and created TrClasspath decorator:

Train<String> train = new TrClasspath<>(new TrDefault<>())

  .with("stylesheet-a.xsl"))

  .with("stylesheet-b.xsl"));

TrClasspath.with() accepts String, turns it into StXSL and passes to TrDefault.with().

Pay attention to the snippet above: the train is now of type Train<String>, not Train<Shift>, as would be required by Xsline. The question now is: how do we get back to Train<Shift>?

Ah, I forgot to mention. I wanted to design this library with one important principle in mind, suggested in 2014: all objects may only implement methods from their interfaces. That’s why, I couldn’t just add a method getEncapsulatedTrain() to TrClasspath.

I introduced a new interface Train.Temporary<T> with a single method back() returning Train<T>. The class TrClasspath implements it and I can do this:

Train<Shift> train = new TrClasspath<>(new TrDefault<>())

  .with("stylesheet-a.xsl"))

  .with("stylesheet-b.xsl"))

  .back();

Next I decided to get rid of the duplication of .with() calls. Obviously, it would be easier to have the ability to provide a list of file names as an array of String and build the train from it. I created a new class TrBulk, which does exactly that:

Iterable<String> names = Arrays.asList(

  "stylesheet-a.xsl",

  "stylesheet-b.xsl"

);

Train<Shift> train = new TrBulk<>(

  new TrClasspath<>(

    new TrDefault<>()

  )

).with(names).back();

With this design I can construct the train in almost any possible way.

See, for example, how we use it here and here.

Source: javacodegeeks.com

Monday, August 8, 2022

Developers disassemble! Use Java and hsdis to see it all.

Use the HotSpot Disassembler to see what’s happening to your code.

Figure 1 below is what you might see when you ask your Java Virtual Machine (JVM) to show you the output of a just-in-time (JIT) compilation performed by the HotSpot JVM after it optimized your program to take advantage of the powerful features in your CPU.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 1. An image of disassembled Java native code

This output comes from a library called hsdis (HotSpot Disassembler). It’s a plugin for the HotSpot JVM, Oracle’s default JVM for Java, which provides disassembly of JIT-compiled native code back into human-readable assembly language code.

There’s a lot to unpack here so let’s start with a refresher on how HotSpot executes Java programs.

Once you have written a program in Java, and before you can run it, you must compile the program’s Java source code into bytecode, the language of the JVM. This compilation can happen in your IDE, via your build tools (such as Maven or Gradle), or via command-line tools such as javac.

The bytecode is written into class files. When you run the program, the class files are loaded into the JVM, and then the bytecode is executed by the JVM’s bytecode interpreter.

While the JVM is running your program, the JVM also profiles the application in real-time order to decide which parts are performed a lot (the hot spots) and might benefit from being compiled from bytecode into native code for the CPU on which your JVM is running.

This transformation from interpreted bytecode into native execution on your CPU is performed at runtime and is known as JIT compilation, in contrast to ahead-of-time (AOT) compilation used by languages such as C/C++. One of the advantages of JIT compilation over AOT compilation is the ability to optimize based on observed behavior at runtime, which is known as profile-guided optimization (PGO).

HotSpot JIT compilation isn’t your only choice, of course. Java programs can also be AOT-compiled using the GraalVM Native Image technology, which might be of interest to you if faster startup time and lower runtime memory overhead are important to you, such as in a serverless microservices environment.

Because JIT compilation happens at runtime, the compilation process consumes CPU and memory resources that might otherwise be used by the JVM to run your application. That means there’s a performance cost that is not present with AOT-compiled binaries.

For the rest of this article, I’ll discuss JIT compilation using HotSpot, examining exactly how that process works.

Java client and Java server


HotSpot contains two separate JIT compilers. The first is C1 (often called the client compiler); the other is C2 (the server compiler):

◉ The C1 compiler begins working quickly and uses fast, simple optimizations to help boost application startup time. In other words, your program starts up faster.
◉ The C2 compiler spends longer collecting the profiling information needed to support more-advanced optimization techniques. Thus, your program takes a little longer to start up but will usually run faster once it has started.

The “client” and “server” parts of the names were assigned during a time in Java’s history when the performance characteristics of a typical end user device (such as a PC or laptop) and a server were very different. For example, back in the days of Java 5, the 32-bit Windows Java Runtime Environment (JRE) distribution contained only the C1 compiler because a typical desktop PC might have only one or two CPU threads. In that era, slowing the startup of a desktop program’s execution to perform advanced C2 optimizations would have a negative effect on the end user’s experience.

Today, end user devices are closer to servers in their processing power. Recently, the default behavior of the JVM has been to combine the strengths of both C1 and C2 in a mode known as tiered compilation, which gives the benefits of the faster optimizations of C1 and the higher peak performance of C2.

Tiered compilation can be controlled using the -XX:+TieredCompilation and -XX:-TieredCompilation switches.

How the HotSpot JIT compilers work


The discussion below summarizes how the HotSpot JIT compilers work and provides enough depth for the purposes of this article. If you want to probe more deeply, you can view the HotSpot compiler source code’s compilation policy.

The basic unit of JIT compilation is the method, and the JVM will use invocation counters to discover which methods are being called most frequently. These are termed the hot methods.

The JIT system can operate at a unit smaller than a whole method when it observes a loop with many back branches, meaning the loop reaches the end of its body and decides it has not finished and needs to jump back to the start for another iteration. When the number of back branches (known as the loop back-edge counter) reaches a threshold, the JIT system can replace the interpreted loop bytecode with a JIT-compiled version. This is known as an on-stack replacement (OSR) compilation.

The code cache. Native code produced by the JIT compilers is stored in a memory region of the JVM called the code cache. Prior to JDK 9, the code cache was a single contiguous piece of memory where the following three main types of native code found in the JVM were stored together:

◉ Profiled code
◉ Nonprofiled (fully optimized) code
◉ JVM internal code

The nonsegmented code cache size was controlled by the -XX:ReservedCodeCacheSize=n switch.

Beginning with JDK 9, the code cache layout was improved by JEP 197 (Segmented code cache). This JEP splits the cache into three regions, depending on the three native code types, to reduce fragmentation and better manage the native code footprints. The segmented code cache sizes are controlled by the following three switches:

◉ -XX:NonProfiledCodeHeapSize=n sets the size in bytes of the code heap containing nonprofiled methods.
◉ -XX:ProfiledCodeHeapSize=n sets the size in bytes of the code heap containing profiled methods.
◉ -XX:NonMethodCodeHeapSize=n sets the size in bytes of the code heap containing nonmethod code.

JEP draft 8279184 (named “Instruction issue cache hardware accommodation”) aims to improve the code cache performance even further.

JIT compilation without tiered compilation. Table 1 shows the thresholds for triggering method compilation on x86 systems if tiered compilation is disabled.

Table 1. Triggering thresholds

Compiler     Invocations
C1                 1,500
C2             10,000

The invocation threshold can be controlled using the -XX:CompileThreshold=n switch. If you wish to control the thresholds for OSR compilation, you can express the back-edge trigger as a percentage of the CompileThreshold value using the -XX:OnStackReplacePercentage=n switch.

JIT with tiered compilation. When tiered compilation is enabled (it’s been the default since JDK 8), the JIT system will use the five tiers of optimization shown in Table 2. A method may end up being JIT-compiled multiple times at different tiers as the JVM better understands the method's usage through profiling.

Table 2. Tiers of optimization

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

The trigger thresholds at each tier on a typical Linux x86 system are as follows:

◉ The tier 2 compile threshold is 0.
◉ The tier 3 invocation threshold is 200.
◉ The tier 3 compile threshold is 2,000.
◉ The tier 4 invocation threshold is 5,000.
◉ The tier 4 compile threshold is 15,000.

Some typical compilation sequences are shown in Table 3.

Table 3. Typical compilation sequences

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Compiler threads. The JVM allows you to control the number of JIT compiler threads with the -XX:CICompilerCount=n switch. Each compiler thread contains a queue, and when a method or loop reaches a compilation threshold, a compilation task will be created and inserted into one of the compiler queues. When a compilation task is removed from the queue, it is passed to the JIT compiler for transformation into optimized native code that is stored in the code cache. See Figure 2 for details.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 2. Compilation tasks in the JIT compiler queues

The role of the HotSpot Disassembler


The OpenJDK HotSpot creators added a feature that allows developers to inspect the native code that is created by the JIT compilers and stored in the code cache. However, this compiled code is in binary format ready for execution by the CPU, so it is not human-readable code.

Fortunately, you can use hsdis, the HotSpot Disassembler, to turn that native code back into a human-readable assembly language code.

When the JVM starts up, it checks for the presence of the hsdis library and if it’s found, the JVM will allow you to use additional switches to control the disassembly output for various types of native code. These hsdis switches are all categorized as diagnostic, so you must first unlock them using the -XX:+UnlockDiagnosticVMOptions switch.

Once they are unlocked, you can request disassembly output by using the switches shown in Table 4.

Table 4. Switches for requesting disassembly output

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Note that when it’s enabled, hsdis is invoked after each blob of native code is inserted into the code cache. The process of disassembling the native code (which can be quite sizable if extensive inlining occurred) instruction by instruction into human-readable assembly language code (a large chunk of text) and writing that text to the console or a log file is a fairly expensive operation best done in your development environment and not in production.

By the way, if you wish to see only the disassembly of specific methods, you can control the output via the -XX:CompileCommand switch. For example, to output the assembly language code for the method length() in java.lang.String you would use

java -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,java/lang/String.length

Building hsdis. The hsdis source files found in the OpenJDK repository are wrapper code. The actual disassembly is performed by external libraries found within the GNU Binutils project.

Let’s say you want to build hsdis using OpenJDK 17 and binutils version 2.37. The hsdis plugin must be compiled for each operating system on which you wish to use it. The hsdis build process will produce a file in the dynamic library format for each operating system, as shown in Table 5.

Table 5. File formats according to OS

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

These instructions assume that you have a working UNIX-like build environment available. If you would prefer to use a prebuilt hsdis plugin as a binary for your operating system and architecture, I’ve built them for your convenience. Download them here, and see Figure 3 for the options.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 3. The prebuilt hsdis binaries

Want to build your own? Here’s what to do.

◉ Download binutils-2.37 here.
◉ Unpack the binutils download using the tar -xzf binutils-2.37.tar.gz command. The build examples below assume you’re unpacking into your home directory.
◉ Clone the OpenJDK 17 source code from GitHub by using git clone.

Note that on recent JDKs, the hsdis files are in the folder <jdk>/src/utils/hsdis.

Below are some specific builds.

Building on Linux (amd64). Perform the following steps:

cd jdk17/src/utils/hsdis
make BINUTILS=~/binutils-2.37 ARCH=amd64
# Produces build/linux-amd64/hsdis-amd64.so

Building on Linux (32-bit ARM such as the Raspberry Pi v1 or v2). Perform the following steps:

cd jdk17/src/utils/hsdis
make BINUTILS=~/binutils-2.37 ARCH=arm
# Produces build/linux-arm/hsdis-arm.so

Building on Linux (64-bit ARM such as the Raspberry Pi v3, v4, or 400). Perform the following steps:

cd jdk17/src/utils/hsdis
make BINUTILS=~/binutils-2.37 ARCH=aarch64
# Produces build/linux-aarch64/hsdis-aarch64.so

Building on macOS (amd64). Perform the following steps:

cd jdk17/src/utils/hsdis
make BINUTILS=~/binutils-2.37 ARCH=amd64
# Produces build/macosx-amd64/hsdis-amd64.dylib

Building on MacOS (ARM M1). Perform the following steps:

cd jdk17/src/utils/hsdis
make BINUTILS=~/binutils-2.37 ARCH=aarch64
# Produces build/macosx-aarch64/hsdis-aarch64.dylib

Building on Windows (using Cygwin and MinGW). Building hsdis on Windows is more involved and uses the Cygwin tools, which provide a Linux-like build environment. The following steps have been tested on Windows 10.

First, download and install Cygwin using the installer found here.

Then, install the following additional packages:

gcc-core                   11.2.0-1
mingw64-x86_64-gcc-core    11.2.0-1
mingw64-x86_64-gcc-g++     11.2.0-1
make                          4.3-1

If you didn’t select these packages at installation time, rerun the installer to select additional packages.

Then to build a Windows 64-bit DLL file, perform the following step:

make OS=Linux MINGW=x86_64-w64-mingw32 BINUTILS=~/binutils-2.37/ ARCH=amd64
# Produces build/Linux-amd64/hsdis-amd64.dll

Alternatively, to build a Windows 32-bit DLL file, perform the following step:

make OS=Linux MINGW=x86_64-w64-mingw3 BINUTILS=~/binutils-2.37/ ARCH=i386
# Produces build/Linux-i586/hsdis-i386.dll

Installing hsdis


Now that you have built or downloaded hsdis, you must put it in a place where the JVM can find it. The following JDK paths are searched:

<JDK_HOME>/lib/server/hsdis-<arch>.<extension>
<JDK_HOME>/lib/hsdis-<arch>.<extension>

Additionally, the paths shown in Table 6 are searched on each operating system.

Table 6. Additional search paths

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Hint: I recommend using an environment variable to point to hsdis. This saves you from having to copy the binary into every JDK you use.

Experimenting in the JITWatch sandbox


If you have experience with C++, you may be familiar with the Compiler Explorer by Matt Godbolt, which lets you test snippets of C++ code using various AOT compilers and view the native code that’s produced.

In the Java world you can do something similar with a tool I’ve written called JITWatch. JITWatch processes the JIT compilation logs that are output by the JVM and explains the optimization decisions made by the JIT compilers.

JITWatch has a sandbox mode in which you can experiment with Java programs in a built-in editor, and then you can click a single button to compile and execute your programs—and inspect the JIT behavior in the main JITWatch user interface. I will use the sandbox to illustrate and explain the output of hsdis. Figure 4 shows the rapid feedback loop with JITWatch.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 4. Rapid feedback loop for JIT experimentation with JITWatch

Installing JITWatch. Download the latest release (at the time of writing, it’s version 1.4.7) from my GitHub repo or build from source code using the following:

git clone https://github.com/AdoptOpenJDK/jitwatch.git
cd jitwatch
mvn clean package
# Produces an executable jar in ui/target/jitwatch-ui-shaded.jar

Run JITWatch with the following command:

java -jar <path to JITWatch jar>

The sandbox comes with a set of examples that exercise various JIT optimizations.

An assembly language primer


Before looking at the assembly language code from an example Java program in JITWatch, here’s a short primer in Intel x86-64 assembly language. For this platform, each disassembled instruction in assembly language takes the following form:

<address> <instruction mnemonic> <operands>

◉ The address is shown for identifying the target of a jump instruction.
◉ The mnemonic is the short name for the instruction.
◉ The operands can be registers, constants, addresses, or a mixture (in the case of offset addressing).

Note that by default hsdis outputs a format known as AT&T assembly, which orders the instructions as follows:

<mnemonic> <src> <dst>

But you can switch to the following Intel format by using the JVM’s -XX:PrintAssemblyOptions=intel switch:

<mnemonic> <dst> <src>

x86-64 register naming. There are many resources describing the x86-64 architecture. The official software developer’s manuals for the Intel 64 and IA-32 architectures are almost 5,000 pages! For the purpose of gaining a basic understanding of the hsdis output, Table 7 shows some of the commonly used registers and their purposes.

Table 7. Commonly used registers

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Note that the rax, rbx, rcx, and rdx registers can be accessed in 32-, 16-, and 8-bit modes by referring to them as shown in Figure 5. When it’s dealing with the Java int type (32 bits), the accumulator will be accessed as eax, and when it’s dealing with the Java long type (64 bits), the accumulator will be accessed as rax.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 5. The rax and eax registers

Registers r8 to r15 can be also accessed in 32-, 16-, and 8-bit modes using a suffix; see Figure 6.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 6. The r8 register

Common instructions. Table 8 shows some commonly encountered assembly language instructions and their meaning. This information is in the Intel format.

Table 8. Common assembly language instructions

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

There is an excellent post on Stack Overflow explaining the benefits behind several nonobvious assembly language idioms.

Using JITWatch to inspect native code


It’s time to run some Java code and inspect the disassembled JIT output. The following example is a simple program that tests whether the JIT compilers can optimize a simple loop that modifies an array. The code creates an array of 1,024 int elements and uses the incrementArray(int[] array, int constant) method to access each element of the array in order, adding a constant to the value of each array element and storing it back in the array.

Here is the source code.

public class DoesItVectorise
{
    public DoesItVectorise()
    {
        int[] array = new int[1024];

        for (int i = 0; i < 1_000_000; i++)
        {
            incrementArray(array, 1);
        }

        for (int i = 0; i < array.length; i++)
        {
            System.out.println(array[i]);
        }
    }

    public void incrementArray(int[] array, int constant)
    {
        int length = array.length;

        for (int i = 0; i < length; i++)
        {
            array[i] += constant;
        }
    }

    public static void main(String[] args)
    {
        new DoesItVectorise();
    }
}

The Java bytecode for the incrementArray method is the following:

 0: aload_1                     // load the reference of 'array'
 1: arraylength                 // call the 'arraylength' instruction to get the length of the array
 2: istore_3                    // store the array length into local variable 3 'length'
 3: iconst_0                    // push int 0 onto the stack
 4: istore          4           // store into local variable 4 'i'
 6: iload           4           // load local variable 4 'i' and push onto the stack
 8: iload_3                     // load local variable 3 'length' and push onto the stack
 9: if_icmpge       26          // if (i >= length) jump to BCI 26 (return)
12: aload_1                     // else load the reference of 'array' and push onto the stack
13: iload           4           // load local variable 4 'i' and push onto the stack
15: dup2                        // duplicate the top 2 values on the stack
16: iaload                      // load the value of array[i] and push onto the stack
17: iload_2                     // load local variable 2 'constant' and push onto the stack 
18: iadd                        // add array[i] and 'constant' and push result onto stack
19: iastore                     // store the result back into array[i]
20: iinc            4, 1        // increment local variable 4 'i' by 1
23: goto            6           // jump back to BCI 6
26: return

The bytecode is a faithful representation of the Java source code with no optimizations performed.

The class to be tested is called DoesItVectorise, and it asks whether the JIT can identify an opportunity to use the features of a modern CPU to vectorize the program so that it can update more than one array element per loop iteration using the wide SIMD registers.

These registers can pack multiple 32-bit int elements into a single register and modify them all with a single CPU instruction, thereby completing the array update with fewer loop iterations.

The incrementArray method is called 1 million times, which should be enough for the JIT compiler to obtain a good profile of how the method behaves.

I’ll load this program into the JITWatch sandbox and see what happens on my Intel i7-8700 CPU. This chip supports the Intel SSE4.2 and AVX2 instruction sets, which means the xmm 128-bit and ymm 256-bit registers should be available for a vectorization optimization.

Step 1: Start JITWatch and open the sandbox, as shown in Figure 7.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 7. The main JITWatch interface

Step 2: Open DoesItVectorise.java from the samples and click Run. Note that if hsdis is not detected, JITWatch will offer to download it for the current OS and architecture. See Figure 8.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 8. The JITWatch sandbox interface showing the code example

Step 3: After execution is complete, locate the JITWatch main window and inspect the compilations of the incrementArray method. On my computer, that method was JIT-compiled four times before reaching its final state of optimization—and that all happened in the first 100 milliseconds of the program’s execution. See Figure 9.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 9. The method was compiled four times.

Step 4: Switch to the three-view screen and ensure that the final (fourth, on my system) compilation is selected. If hsdis is correctly installed on your system, you will see the disassembly output for the incrementArray method, as shown in Figure 10.

Oracle Java Certified, Oacle Java Certification, Java Exam, Java Prep, Java Exam Preparation, Java Tutorial and Materials, Java Learning

Figure 10. The JITWatch three-view screen showing source, bytecode, and assembly language code

Understanding the JITWatch output


When the HotSpot JIT compiler creates the native code, it includes comments that help someone reading the disassembled code to relate it back to the original program. These comments include bytecode index (BCI) references, which allow JITWatch to relate assembly language instructions to the bytecode of the program. The Java class file format contains a LineNumberTable data structure that JITWatch uses to map the bytecode instructions back to the Java source code.

On entry, register rdx points to the int[] object named array, while rcx contains the int value named constant, as follows:

# {method} {0x00007f92cb000410} 'incrementArray' '([II)V' in 'DoesItVectorise'
# this:     rsi:rsi   = 'DoesItVectorise'
# parm0:    rdx:rdx   = '[I'
# parm1:    rcx       = int

The native code performs a stack bang test to ensure sufficient stack space is available; it does this by attempting to store the contents of eax at a fixed offset from the stack pointer rsp. (If this address falls within a guard page, a StackOverflowException will be thrown.)

[Verified Entry Point]
0x00007f92f523b8c0: mov DWORD PTR [rsp-0x14000],eax

The code then loads the array length (an int) into register ebx. With the -XX:+UseCompressedOops switch enabled, the array object header consists of a 64-bit mark word and a compressed 32-bit klass word, so the array length field is found after these two values at offset [rdx + 12 (0xc) bytes]. (A klass word in an object header points to the internal type metadata for the object.)

0x00007f92f523b8cc: mov ebx,DWORD PTR [rdx+0xc]  ; implicit exception: dispatches to 0x00007f92f523ba71
                                                 ;*arraylength {reexecute=0 rethrow=0 return_oop=0}
                                                 ; - DoesItVectorise::incrementArray@1 (line 20)

The next code tests whether the array length in ebx is 0. If it is, execution jumps to the end of this procedure to clean up and return.

0x00007f92f523b8cf: test ebx,ebx
0x00007f92f523b8d1: jbe L0007  ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
                               ; - DoesItVectorise::incrementArray@9 (line 22)

What you will see next is the assembly language code representing the HotSpot loop unrolling optimization. To reduce array bounds checking, the loop is split into the following three parts:

◉ A preloop sequence
◉ A main unrolled loop performing most of the iterations with no bounds checking
◉ A postloop to complete any final iterations

The next section shows the setup and preloop sequence.

0x00007f92f523b8d7: mov ebp,ebx
0x00007f92f523b8d9: dec ebp
0x00007f92f523b8db: cmp ebp,ebx
0x00007f92f523b8dd: data16 xchg ax,ax
0x00007f92f523b8e0: jae L0008
0x00007f92f523b8e6: mov r11d,edx
0x00007f92f523b8e9: shr r11d,0x2
0x00007f92f523b8ed: and r11d,0x7
0x00007f92f523b8f1: mov r10d,0x3
0x00007f92f523b8f7: sub r10d,r11d
0x00007f92f523b8fa: and r10d,0x7
0x00007f92f523b8fe: inc r10d
0x00007f92f523b901: cmp r10d,ebx
0x00007f92f523b904: cmovg r10d,ebx
0x00007f92f523b908: xor esi,esi
0x00007f92f523b90a: xor eax,eax  ;*aload_1 {reexecute=0 rethrow=0 return_oop=0}
                                 ; - DoesItVectorise::incrementArray@12 (line 24)
             L0000: add DWORD PTR [rdx+rax*4+0x10],ecx  ;*iastore {reexecute=0 rethrow=0 return_oop=0}
                                                        ; - DoesItVectorise::incrementArray@19 (line 24)
0x00007f92f523b910: mov r9d,eax
0x00007f92f523b913: inc r9d  ;*iinc {reexecute=0 rethrow=0 return_oop=0}
                             ; - DoesItVectorise::incrementArray@20 (line 22)
0x00007f92f523b916: cmp r9d,r10d
0x00007f92f523b919: jge L0001  ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
                               ; - DoesItVectorise::incrementArray@9 (line 22)
0x00007f92f523b91b: mov eax,r9d
0x00007f92f523b91e: xchg ax,ax
0x00007f92f523b920: jmp L0000
             L0001: mov r10d,ebx
0x00007f92f523b925: add r10d,0xffffffc1
0x00007f92f523b929: mov r11d,0x80000000
0x00007f92f523b92f: cmp ebp,r10d
0x00007f92f523b932: cmovl r10d,r11d
0x00007f92f523b936: cmp r9d,r10d
0x00007f92f523b939: jge L0009

The next two instructions show the loop is going to be vectorized, as follows:

◉ The value of the 32-bit int constant in general purpose register ecx is now pasted four times across the 128-bit SIMD register xmm0 using the vmovd instruction.
◉ Then, using the vpbroadcastd instruction, the contents of 128-bit register xmm0 are broadcast into 256-bit SIMD register ymm0, which now contains eight copies of the value of constant.

0x00007f92f523b93f: vmovd xmm0,ecx
0x00007f92f523b943: vpbroadcastd ymm0,xmm0

More unrolling setup is next.

0x00007f92f523b948: inc eax
0x00007f92f523b94a: mov r8d,0xfa00
             L0002: mov edi,r10d
0x00007f92f523b953: sub edi,eax
0x00007f92f523b955: cmp r10d,eax
0x00007f92f523b958: cmovl edi,esi
0x00007f92f523b95b: cmp edi,0xfa00
0x00007f92f523b961: cmova edi,r8d
0x00007f92f523b965: add edi,eax
0x00007f92f523b967: nop WORD PTR [rax+rax*1+0x0]  ;*aload_1 {reexecute=0 rethrow=0 return_oop=0}
                                                  ; - DoesItVectorise::incrementArray@12 (line 24)

Here is the unrolled part of the loop, which performs vectorized array addition using the vpaddd and vmovdqu instructions.

◉ vpaddd performs packed integer addition between ymm0 (containing eight copies of constant) and eight int values read from the array, storing the result in 256-bit SIMD register ymm1.
◉ vmovdqu stores the eight incremented packed integers in ymm1 back to their locations in the array.

Between label L0003 and the jl back branch, this pair of instructions appears eight times. So through vectorization and loop unrolling, an impressive 64 array elements are updated per iteration of the main loop section.

The instruction add eax,0x40 confirms that the array offset in eax is incremented by 64 (0x40) at the end of each unrolled loop iteration.

L0003: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0x10]
0x00007f92f523b976: vmovdqu YMMWORD PTR [rdx+rax*4+0x10],ymm1
0x00007f92f523b97c: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0x30]
0x00007f92f523b982: vmovdqu YMMWORD PTR [rdx+rax*4+0x30],ymm1
0x00007f92f523b988: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0x50]
0x00007f92f523b98e: vmovdqu YMMWORD PTR [rdx+rax*4+0x50],ymm1
0x00007f92f523b994: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0x70]
0x00007f92f523b99a: vmovdqu YMMWORD PTR [rdx+rax*4+0x70],ymm1
0x00007f92f523b9a0: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0x90]
0x00007f92f523b9a9: vmovdqu YMMWORD PTR [rdx+rax*4+0x90],ymm1
0x00007f92f523b9b2: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0xb0]
0x00007f92f523b9bb: vmovdqu YMMWORD PTR [rdx+rax*4+0xb0],ymm1
0x00007f92f523b9c4: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0xd0]
0x00007f92f523b9cd: vmovdqu YMMWORD PTR [rdx+rax*4+0xd0],ymm1
0x00007f92f523b9d6: vpaddd ymm1,ymm0,YMMWORD PTR [rdx+rax*4+0xf0]
0x00007f92f523b9df: vmovdqu YMMWORD PTR [rdx+rax*4+0xf0],ymm1  ;*iastore {reexecute=0 rethrow=0 return_oop=0}
                                                               ; - DoesItVectorise::incrementArray@19 (line 24)
0x00007f92f523b9e8: add eax,0x40  ;*iinc {reexecute=0 rethrow=0 return_oop=0}
                                  ; - DoesItVectorise::incrementArray@20 (line 22)
0x00007f92f523b9eb: cmp eax,edi
0x00007f92f523b9ed: jl L0003  ;*goto {reexecute=0 rethrow=0 return_oop=0}
                              ; - DoesItVectorise::incrementArray@23 (line 22)

The rest of the assembly language code (which I have omitted for conciseness) contains the postloop iterations and the assembly language epilogue used to clean up the stack and return. Note that there is no return value from the incrementArray method.

Source: oracle.com