Showing posts with label Java 17. Show all posts
Showing posts with label Java 17. Show all posts

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

Wednesday, June 8, 2022

Bruce Eckel on Java pattern matching guards and dominance

Core Java, Java Career, Java Jobs, Java Skills, Java Preparation, Oracle Java Certification, Java Guides, Oracle Java Tutorial and Materials

Pattern matching guards let you refine the matching condition beyond simply matching on the type.

The previous article in this series, “Bruce Eckel on pattern matching in Java,” introduced pattern matching and this article delves into the details. Keep in mind that, at the time of this writing, pattern matching for switch is a second preview feature in JDK 18 as JEP 420, and the Java team intends to add additional features.

Guards

A guard allows you to refine the matching condition beyond simply matching on the type. It is a test that appears after the type and &&. The guard can be any Boolean expression. If the selector expression is the same as the type for the case and the guard evaluates to true, the pattern matches, as follows:

// enumerations/Shapes.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Shape {

  double area();

}

record Circle(double radius) implements Shape {

  @Override public double area() {

    return Math.PI * radius * radius;

  }

}

record Rectangle(double side1, double side2)

  implements Shape {

  @Override public double area() {

    return side1 * side2;

  }

}

public class Shapes {

  static void classify(Shape s) {

    System.out.println(switch(s) {

      case Circle c && c.area() < 100.0

        -> "Small Circle: " + c;

      case Circle c -> "Large Circle: " + c;

      case Rectangle r && r.side1() == r.side2()

        -> "Square: " + r;

      case Rectangle r -> "Rectangle: " + r;

    });

  }

  public static void main(String[] args) {

    List.of(

      new Circle(5.0),

      new Circle(25.0),

      new Rectangle(12.0, 12.0),

      new Rectangle(12.0, 15.0)

    ).forEach(t -> classify(t));

  }

}

/* Output:

Small Circle: Circle[radius=5.0]

Large Circle: Circle[radius=25.0]

Square: Rectangle[side1=12.0, side2=12.0]

Rectangle: Rectangle[side1=12.0, side2=15.0]

*/

(The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

The first guard for Circle determines whether that Circle is small. The first guard for Rectangle determines whether that Rectangle is a square.

Here’s a more complex example: A Tank can hold different types of liquids, and the Level of the tank must be between zero and 100%.

// enumerations/Tanks.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

enum Type { TOXIC, FLAMMABLE, NEUTRAL }

record Level(int percent) {

  Level {

    if(percent < 0 || percent > 100)

      throw new IndexOutOfBoundsException(

        percent + " percent");

  }

}

record Tank(Type type, Level level) {}

public class Tanks {

  static String check(Tank tank) {

    return switch(tank) {

      case Tank t && t.type() == Type.TOXIC

        -> "Toxic: " + t;

      case Tank t && (                 // [1]

          t.type() == Type.TOXIC &&

          t.level().percent() < 50

        ) -> "Toxic, low: " + t;

      case Tank t && t.type() == Type.FLAMMABLE

        -> "Flammable: " + t;

      // Equivalent to "default":

      case Tank t -> "Other Tank: " + t;

    };

  }

  public static void main(String[] args) {

    List.of(

      new Tank(Type.TOXIC, new Level(49)),

      new Tank(Type.FLAMMABLE, new Level(52)),

      new Tank(Type.NEUTRAL, new Level(75))

    ).forEach(

      t -> System.out.println(check(t))

    );

  }

}

The record Level includes a compact constructor that ensures that percent is valid. Records were introduced in a previous article, “Bruce Eckel on Java records,” in this series.

Here’s a note for line [1]: If a guard contains multiple expressions, simply enclose it in parentheses.

Since the code switches on Tank rather than Object, the final case Tank acts the same as a default because it catches all Tank cases that don’t match any of the other patterns.

Dominance

The order of the case statements in a switch can be important because if the base type appears first, it dominates anything appearing afterwards.

// enumerations/Dominance.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

import java.util.*;

sealed interface Base {}

record Derived() implements Base {}

public class Dominance {

  static String test(Base base) {

    return switch(base) {

      case Derived d -> "Derived";

      case Base b -> "B";            // [1]

    };

  }

}

The base type Base is in last place, at line [1]—and that’s where it should be. But if you move that line up, the base type will appear before case Derived, which would mean that the switch would never be able to test for Derived because any derived class would then be captured by case Base. If you try this experiment, the compiler reports an error: this case label is dominated by a preceding case label.

Order sensitivity often appears when you use guards. Moving the final case in Tanks.java to a higher position in the switch produces that same domination error message. When you have multiple guards on the same pattern, more-specific patterns must appear before more-general patterns. Otherwise, a more-general pattern will match before more-specific patterns, and the latter will never be checked. Fortunately, the compiler reports dominance problems.

The compiler can detect dominance problems only when the type in a pattern dominates the type in another pattern. The compiler cannot know whether the logic in guards produces problems.

// enumerations/People.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

record Person(String name, int age) {}

public class People {

  static String categorize(Person person) {

    return switch(person) {

      case Person p && p.age() > 40          // [1]

        -> p + " is middle aged";

      case Person p &&

        (p.name().contains("D") || p.age() == 14)

        -> p + " D or 14";

      case Person p && !(p.age() >= 100)     // [2]

        -> p + " is not a centenarian";

      case Person p -> p + " Everyone else";

    };

  }

  public static void main(String[] args) {

    List.of(

      new Person("Dorothy", 15),

      new Person("John Bigboote", 42),

      new Person("Morty", 14),

      new Person("Morty Jr.", 1),

      new Person("Jose", 39),

      new Person("Kane", 118)

    ).forEach(

      p -> System.out.println(categorize(p))

    );

  }

}

/* Output:

Person[name=Dorothy, age=15] D or 14

Person[name=John Bigboote, age=42] is middle aged

Person[name=Morty, age=14] D or 14

Person[name=Morty Jr., age=1] is not a centenarian

Person[name=Jose, age=39] is not a centenarian

Person[name=Kane, age=118] is middle aged

*/

The guard in pattern line [2] seems like it would match Kane at age 118, but instead Kane matches with the pattern at line [1]. You cannot rely on the compiler to help with the logic of your guard expressions.

Without the last case Person p, the compiler complains that the switch expression does not cover all possible input values. With that case, a default is still not required, so the most general case becomes the default. Because the argument to the switch is a Person, all cases are covered (except for null).

Coverage

Pattern matching naturally guides you toward using the sealed keyword. This helps ensure that you’ve covered all possible types passed into the selector expression. See how that works in practice with the following example:

// enumerations/SealedPatternMatch.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Transport {};

record Bicycle(String id) implements Transport {};

record Glider(int size) implements Transport {};

record Surfboard(double weight) implements Transport {};

// If you uncomment this:

// record Skis(int length) implements Transport {};

// You get an error: "the switch expression

// does not cover all possible input values"

public class SealedPatternMatch {

  static String exhaustive(Transport t) {

    return switch(t) {

      case Bicycle b -> "Bicycle " + b.id();

      case Glider g -> "Glider " + g.size();

      case Surfboard s -> "Surfboard " + s.weight();

    };

  }

  public static void main(String[] args) {

    List.of(

      new Bicycle("Bob"),

      new Glider(65),

      new Surfboard(6.4)

    ).forEach(

      t -> System.out.println(exhaustive(t))

    );

    try {

      exhaustive(null); // Always possible!  // [1]

    } catch(NullPointerException e) {

      System.out.println("Not exhaustive: " + e);

    }

  }

}

/* Output:

Bicycle Bob

Glider 65

Surfboard 6.4

Not exhaustive: java.lang.NullPointerException

*/

The sealed interface Transport is implemented using record objects, which are automatically final. The switch covers all possible types of Transport, and if you add a new type, the compiler detects it and tells you that you haven’t exhaustively covered all possible patterns. But line [1] shows that there’s still one case that the compiler doesn’t insist you cover: null.

If you remember to explicitly add a case null, you’ll prevent the exception. But the compiler doesn’t help you here, possibly because that would affect too much existing switch code.

Source: oracle.com

Tuesday, May 24, 2022

Bruce Eckel on pattern matching in Java

pattern matching in Java, Core Java, Java Exam Prep, Java Tutorial and Material, Java Career, Java Skills, Java Jobs, Java News, Java Certifications

You can currently use pattern variables for instanceof and for switch. See how they work.

JDK 16 finalized JDK Enhancement Proposal (JEP) 394, Pattern matching for instanceof. Below you can see the old and new ways of doing the same thing.

// enumerations/SmartCasting.java

// {NewFeature} Since JDK 16

public class SmartCasting {

  static void dumb(Object x) {

    if(x instanceof String) {

      String s = (String)x;

      if(s.length() > 0) {

        System.out.format(

          "%d %s%n", s.length(), s.toUpperCase());

      }

    }

  }

  static void smart(Object x) {

    if(x instanceof String s && s.length() > 0) {

      System.out.format(

        "%d %s%n", s.length(), s.toUpperCase());

    }

  }

  static void wrong(Object x) {

    // "Or" never works:

    // if(x instanceof String s || s.length() > 0) {}

    // error: cannot find symbol   ^

  }

  public static void main(String[] args) {

    dumb("dumb");

    smart("smart");

  }

}

/* Output:

4 DUMB

5 SMART

*/

(The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

In dumb(), once instanceof establishes that x is a String, you must explicitly cast it to String s. Otherwise, you would be inserting casts throughout the rest of the function. But in smart(), notice the x instanceof String s.

This automatically creates a new variable s of type String. Note that s is available throughout the scope, even within the remainder of the if conditional, as you see in && s.length() > 0. This produces more-compact code.

In constrast, wrong() shows that only && can be used in a pattern-matching if expression. Using || would mean that x is an instanceof String or that s.length() > 0. That would mean x might not be a String, in which case Java could not smart-cast x to create s; therefore, s would not be available on the right side of the ||.

JEP 394 calls s a pattern variable.

Although this feature does clean up some messy if statements, that wasn’t the motivation for adding it. It was included as a building block for pattern matching, as you will see shortly.

By the way, this feature can produce some strange scoping behavior such as the following:

// enumerations/OddScoping.java

// {NewFeature} Since JDK 16

public class OddScoping {

  static void f(Object o) {

    if(!(o instanceof String s)) {

      System.out.println("Not a String");

      throw new RuntimeException();

    }

    // s is in scope here!

    System.out.println(s.toUpperCase());  // line [1]

  }

  public static void main(String[] args) {

    f("Curiouser and Curiouser");

    f(null);

  }

}

/* Output:

CURIOUSER AND CURIOUSER

Not a String

Exception in thread "main" java.lang.RuntimeException

        at OddScoping.f(OddScoping.java:8)

        at OddScoping.main(OddScoping.java:15)

*/

In line [1], s is in scope only if you don’t include the statement that throws the exception. If you comment out throw new RuntimeException(), the compiler tells you it can’t find s in line [1], which is the behavior you normally would expect.

Initially this might look like a bug, but it is designed that way; this behavior is explicitly described in JEP 394. Although this is arguably a corner case, you can imagine how difficult it might be to track down a bug caused by this behavior.

Pattern matching

Now that all the foundational pieces are in place, you can look at the bigger picture of pattern matching in Java.

At the time of this writing, pattern matching for instanceof was delivered in JDK 16 as JEP 394. Pattern matching for switch was a preview feature in JDK 17 as JEP 406, and there was a second preview in JDK 18 as JEP 420. This means pattern matching probably won’t change or change significantly in future versions, but it hasn’t been finalized. By the time you read this, the features shown here could be out of preview and so you might not need to use the compiler and runtime flags specified in the example comments.

Violating the Liskov Substitution Principle

Inheritance-based polymorphism performs type-based behavior but requires those types to be in the same inheritance hierarchy. Pattern matching allows you to perform type-based behavior on types that don’t all have the same interface or are not in the same hierarchy.

This is a different way to use reflection. You still determine types at runtime, but this is a more formalized and structured way to do it than reflection.

If the types of interest all have a common base type and use only methods defined in that common base type, you are conforming to the Liskov Substitution Principle (LSP). In this case, pattern matching is unnecessary, because you can just use normal inheritance polymorphism, as follows:

// enumerations/NormalLiskov.java

import java.util.stream.*;

interface LifeForm {

  String move();

  String react();

}

class Worm implements LifeForm {

  @Override public String move() {

    return "Worm::move()";

  }

  @Override public String react() {

    return "Worm::react()";

  }

}

class Giraffe implements LifeForm {

  @Override public String move() {

    return "Giraffe::move()";

  }

  @Override public String react() {

    return "Giraffe::react()";

  }

}

public class NormalLiskov {

  public static void main(String[] args) {

    Stream.of(new Worm(), new Giraffe())

      .forEach(lf -> System.out.println(

        lf.move() + " " + lf.react()));

  }

}

/* Output:

Worm::move() Worm::react()

Giraffe::move() Giraffe::react()

*/

All methods are neatly defined in the LifeForm interface, and no new methods are added in any of the implemented classes.

But what if you need to add methods that can’t easily be placed in the base type? Some worms can reproduce when they are divided, for example, and a giraffe certainly can’t do that. A giraffe can kick, but it’s hard to imagine how you’d represent that in the base class in a way that it didn’t make the Worm implementation problematic.

The Java Collections library ran into this problem and attempted to solve it by adding optional methods in the base type that were implemented in some subclasses but not in others. This approach conformed to LSP but produced a confusing design.

Java was fundamentally inspired by Smalltalk, a dynamic language that reuses code by taking existing classes and adding methods. A Smalltalk design for a Pet hierarchy might end up looking like the following:

// enumerations/Pet.java

public class Pet {

  void feed() {}

}

class Dog extends Pet {

  void walk() {}

}

class Fish extends Pet {

  void changeWater() {}

}

You can take the basic Pet functionality and extend that class by adding methods as you need them. This is different than what is normally advocated for Java (and shown in NormalLiskov.java), where you carefully design the base type to include all possible methods needed throughout the hierarchy, thus conforming to LSP. Although this is a nice aspirational goal, it can be impractical.

Attempting to force the dynamically typed Smalltalk model into the statically typed Java system is bound to create compromises. In some cases, those compromises might be unworkable. Pattern matching allows you to use Smalltalk’s approach of adding new methods to derived classes while still maintaining most of the formality of LSP. Basically, pattern matching allows you to violate LSP without creating unmanageable code.

With pattern matching, you can deal with the non-LSP nature of the Pet hierarchy by checking for, and writing different code for, each possible type.

// enumerations/PetPatternMatch.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

import java.util.*;

public class PetPatternMatch {

  static void careFor(Pet p) {

    switch(p) {

      case Dog d -> d.walk();

      case Fish f -> f.changeWater();

      case Pet sp -> sp.feed();

    };

  }

  static void petCare() {

    List.of(new Dog(), new Fish())

      .forEach(p -> careFor(p));

  }

}

The p in switch(p) is called the selector expression. Prior to pattern matching, a selector expression could be only an integral primitive type (char, byte, short, or int), the corresponding boxed form (Character, Byte, Short, or Integer), String, or an enum type. With pattern matching, the selector expression is expanded to include any reference type. Here, the selector expression can be a Dog, Fish, or Pet.

Notice that this is similar to dynamic binding within an inheritance hierarchy, but instead of putting the code for different types within the overridden methods, you’ll put it in the different case expressions.

The compiler forced the addition of case Pet because that class can legitimately exist without being a Dog or a Fish. Without case Pet, then, the switch didn’t cover all possible input values. Using an interface for the base type eliminates this constraint but adds a different one. The following example is placed in its own package to prevent name clashes:

// enumerations/PetPatternMatch2.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

package sealedpet;

import java.util.*;

sealed interface Pet {

  void feed();

}

final class Dog implements Pet {

  @Override public void feed() {}

  void walk() {}

}

final class Fish implements Pet {

  @Override public void feed() {}

  void changeWater() {}

}

public class PetPatternMatch2 {

  static void careFor(Pet p) {

    switch(p) {

      case Dog d -> d.walk();

      case Fish f -> f.changeWater();

    };

  }

  static void petCare() {

    List.of(new Dog(), new Fish())

      .forEach(p -> careFor(p));

  }

}

If Pet is not sealed, the compiler complains that the switch statement does not cover all possible input values. In this case, it’s because the interface Pet could be implemented by anyone else in any other file, breaking the exhaustive coverage by the switch statement. By making Pet sealed, the compiler can ensure that the switch covers all possible Pet types.

Pattern matching doesn’t constrain you to a single hierarchy the way inheritance polymorphism does; that is, you can match on any type. For example, to do this, you can pass Object into the switch as follows:

// enumerations/ObjectMatch.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

record XX() {}

public class ObjectMatch {

  static String match(Object o) {

    return switch(o) {

      case Dog d -> "Walk the dog";

      case Fish f -> "Change the fish water";

      case Pet sp -> "Not dog or fish";

      case String s -> "String " + s;

      case Integer i -> "Integer " + i;

      case String[] sa -> String.join(", ", sa);

      case null, XX xx -> "null or XX: " + xx;

      default -> "Something else";

    };

  }

  public static void main(String[] args) {

    List.of(new Dog(), new Fish(), new Pet(),

      "Oscar", Integer.valueOf(12),

      Double.valueOf("47.74"),

      new String[]{ "to", "the", "point" },

      new XX()

    ).forEach(

      p -> System.out.println(match(p))

    );

  }

}

/* Output:

Walk the dog

Change the fish water

Not dog or fish

String Oscar

Integer 12

Something else

to, the, point

null or Object: XX[]

*/

When passing an Object parameter to switch, a default is required by the compiler—again, to cover all possible input values (except for null, for which a case is not required by the compiler, even though it can happen).

It’s possible to combine the null case with a pattern, as seen in case null, XX xx. This works because an object reference can be null.

Source: oracle.com

Friday, February 18, 2022

Java’s new HTTP Client API, UNIX-domain sockets, and Simple Web Server

Oracle Java Certification, Java Certification, Oracle Java Skills, Oracle Java Prep, Java Learning, Oracle Java API, Oracle Java Tutorial and Material

A conversation with two architects about new networking features in the JDK, including a new feature in Java 18

Download a PDF of this article

Networking capabilities were built into Java’s core libraries right from the start. For developers, the fact that networking was built into a major development platform was a welcome change, because organizations didn’t have to rely on third-party libraries or invest resources to create their own low-level networking code to support common networking protocols.

Of course, in the 1990s, not every application needed networking—but now networking support is an absolute requirement for nearly all platforms. Indeed, most of today’s most popular business and consumer applications wouldn’t exist without the ability to connect to services using the HTTP protocol.

That said, Java’s very widely used HTTP Client API hadn’t seen a significant improvement until the new API started to incubate in JDK 9 as JEP 110 and was made standard in JDK 11 as JEP 321.

David Delabassée, a developer advocate in the Java Platform Group at Oracle, recently spoke with Oracle Java architects Daniel Fuchs and Michael McMahon to discuss some of the recent network updates to the Java platform. Here are a few of the highlights from that conversation.

Delabassée: Why was the new Java HTTP Client API introduced?

McMahon: The original, 25-year-old Java HTTP Client API is old and has limitations. It was hard to use and hard to maintain. Part of the API supported protocols that aren’t used anymore, such as Gopher, which adds to complexity.

Plus, the old client is blocking and ties up a thread for the entire duration of a request, regardless of how long that is going to take. In addition, the old client was for the HTTP/1.1 protocol and needed to evolve to support the newer HTTP/2 protocol.

Fuchs: The new HTTP Client API started to incubate in JDK 9 and was later made standard and permanent in JDK 11. It supports both HTTP/1.1 and HTTP/2, supports WebSockets, and improves security. More importantly, the new client is modern and easy to use. For example, it uses the popular Builder pattern.

Delabassee: How does the new client work?

Fuchs: To send a request, you first need to create an HTTP client; there is a builder for that. Using a builder, you create a client instance and configure its state. For example, you can specify which protocol to use (such as HTTP/1.1 or HTTP/2) and whether to follow redirects. You also can set the proxy selector, set the necessary contexts for Transport Layer Security (TLS), and so on.

Once built, your HTTP client can be used to send multiple requests and is immutable. Requests can be sent multiple times, and you can choose whether they are sent synchronously or asynchronously.

Delabassée: Moving beyond the HTTP Client API, UNIX-domain socket channels were added to Java 16. What can you tell us about that?

McMahon: UNIX-domain sockets are like TCP/IP sockets, except that these sockets are used only for local interprocess communication (IPC), and they are addressed through file system pathnames, rather than through an IP address and port number.

UNIX-domain socket channels, which appeared in JEP 380, offer several benefits for applications that need to do only local IPC or IPC between containers. Performance is one benefit: By cutting out the TCP/IP stack, you can improve throughput and lower CPU utilization. These sockets can also improve security because if you need only local IPC, you don’t need to open ports to the network.

With UNIX-domain sockets, you can also make use of file system access controls and user credentials for additional security. When testing them with Docker, we found that UNIX-domain sockets make it easier to set up communication between containers.

To make all this work, JEP 380 added the following API elements:

◉ A new socket address class, java.net.UnixDomainSocketAddress

◉ A UNIX constant value in the existing java.net.StandardProtocolFamily enum

◉ New open factory methods on SocketChannel and ServerSocketChannel that specify the protocol family

◉ Updates to the SocketChannel and ServerSocketChannel specifications to allow you to define how the channels to the UNIX-domain sockets behave

It should also be mentioned that despite the name, UNIX-domain sockets are also available on Windows!

Delabassée: Typically, with a network connection, security is enforced at the network level, such as by using firewalls and reverse proxies. How can security be enforced with UNIX-domain sockets?

McMahon: One level of security is the old Java platform security model. For that, there’s a single permission, which enables or disables the feature if a security manager is running.

Beyond that, the main way you get security is through the operating system, its user and groups, and its file permissions, where you can restrict access to file system nodes through user IDs and group IDs.

An additional useful feature, which works on the UNIX platform but not on Windows, is via testing user credentials. If a client socket channel is connected to a server socket channel, either side can request the identity of the user at the opposite side.

Delabassée: There is a new simple web server and API targeted for Java 18. Can you give us a quick overview?

McMahon: JEP 408 will deliver a simple web server to serve static content. JEP 408 includes an API for programmatic creation and customization of the server and its components.

As its name implies, the Simple Web Server’s design is explicitly minimal. It is intended for development purposes, as well as for testing and educational use, but it is certainly not a full-featured server that you should use in production! For example, it doesn’t offer security features.

The Simple Web Server is based on the web server implementation in the com.sun.net.httpserver package that has been included in the JDK since 2006. That package is officially supported, and it’s extended with APIs to simplify server creation and enhance request handling. The Simple Web Server can be used via the dedicated command-line tool jwebserver or programmatically via its API.

To reiterate, the Simple Web Server is intended for educational, development testing, and debugging purposes only.

Delabassee: Any final thoughts about the new networking capabilities?

Fuchs: We discussed the HTTP Client API, UNIX-domain socket channels, and the Simple Web Server. Those features are quite visible because developers can use them directly. In parallel to those features, the Java teams continue to work on the platform itself, such as on some features that are not so visible but nevertheless are strategic going forward.

For example, slowly but surely Project Loom is coming—and so we are looking at making sure that our networking implementation will play nice with virtual threads. That was the motivation for JEP 353, which reimplements the legacy Socket API, and JEP 373, which reimplements the legacy DatagramSocket API.

Source: oracle.com

Friday, February 11, 2022

Design implications of Java’s switch statements and switch expressions

The two constructs might look similar, but they behave quite differently, and each is best suited for a different type of programmatic logic.

Download a PDF of this article

The recent evolution of the Java language has introduced interesting new features and constructs intended to make developers more productive. And it is one of these features, known as switch expressions, that I’d like to talk about.

I will start with the following assertion: switch statements and switch expressions have different design purposes. One is not just a different version of the other. Rather, switch expressions are a new construct intended for a different purpose than switch statements. To be clear, the older switch statement and the newer switch expression share some syntactical similarities, but they pursue significantly different design goals.

My intention in this article is not to just talk about this new construct from the perspective of its syntax but rather investigate its design implications for developers’ work. Surely, the intended developer productivity improvements are not meant to be measured in how fast you can type but rather in how well Java’s language constructs support coding requirements and help developers convey the meaning of the algorithm they are working on. This means that every Java developer needs to understand the intended purposes of such language constructs.

switch statements are not an alternative if/else construct

Let’s start by addressing one of the most common misconceptions about the switch statement, namely that it is usually explained by comparing it to the if/else statement as an alternative mechanism for implementing conditional logic.

In my opinion, this is not really the best way of understanding the switch statement. The if/else construct is designed to choose an alternate execution path, essentially presenting a binary fork mechanism that selects the way in which program logic would flow, as shown in Figure 1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 1. The if/else construct chooses an alternate execution path.

However, the switch statement feels much more like a forward-only goto mechanism, as shown in Figure 2. It is not about a binary selection of an alternate execution path but rather a way of executing a jump into a block of code at a specified line labeled using a corresponding case. This means that switch statement logic is more like a goto operator than an if/else construct.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 2. The switch statement acts like a forward-only goto mechanism.

Once you understand this principle, the fall-through behavior of the switch statement appears fairly natural: When the switch statement evaluates a value that corresponds to a particular case statement, the execution path jumps directly to that case statement and execution continues from there.

After all, you certainly would expect an algorithm to continue progressing forward as usual after a goto action. As a matter of fact, this could even be exploited to your advantage.

Suppose you need to perform several actions that are not mutually exclusive, and yet not all the actions are always applicable. For example, imagine that you need to set a pricing discount based on product status, as follows (see Figure 3):

◉ For discontinued products, the discount should be increased by 0.2.
◉ For refurbished products, the discount should be increased by 0.1.
◉ For new products, the discount should be decreased by 0.1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 3. Example of a "classic" switch statement

In addition to the above rules, there are two extra business constraints:

◉ Some discontinued products may also be refurbished, in which case both discounts should be applied.
◉ New products can’t be classified as refurbished or discontinued.

Notice that the first two cases (for discontinued and refurbished products) have an additive nature rather than a mutually exclusive nature. The use of the fall-through switch/case construct is perfectly suitable to reflect this type of business requirement, and the resulting code is more readable than an attempt to express the same logic with a complicated if/else construct. Of course, the same logic could be expressed using several if conditions, but it is obvious that the resulting code would be less straightforward and possibly harder to read, harder to test, and harder to maintain.

I believe in the great value of source code readability. Often, the same logic can be expressed in a program in many ways, so it may be worth considering readability as an important deciding factor when you code constructs. After all, code written in a convoluted and confusing fashion can be prone to errors and misinterpretations.

Selecting the best-matching semantical construct in a program is probably as important as in a human conversation: If you want to be understood, you should express your thoughts clearly, concisely, and unambiguously. (Similarly, coding is a conversation between you and the compiler—and also between you and whoever will be maintaining your code in the future.)

switch expression case statements don’t fall-through


Now let’s consider the design ideas behind a switch expression. The first thing to notice is that switch expression case statements are mutually exclusive and do not use the fall-through mechanism. That is because of the different purpose that switch expression design pursues, which is to derive and return a single value.

The expectation is that executing a switch expression yields a single value, and that case statements within the switch simply represent different ways of deriving that value, as shown in Figure 4.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 4. The switch expression uses case to represent different value derivations

The need to return a single value implies the mutually exclusive nature of these case statements; thus, switch expression case statements do not fall through and also they do not allow the use of a break statement.

Consider a business scenario where a product’s discount is dependent on a single, specific case. The main difference from the earlier example is that the discount for the discontinued and refurbished products is the same, but discount values are not added one after another, as shown in Figure 5. The -> arrow token indicates that you are using a switch expression rather than a switch statement.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 5. Example of using a switch expression to derive a value

Of course, this switch is now capable of returning a value, and it also needs to be terminated with a semicolon.

The style of the switch expression makes it somewhat like a SQL decode operator, treating the entire switch as a kind of value-derivation function.

There is one more consideration that is checked by a compiler, which is the need to guarantee that no matter what value is yielded from the expression, the list of case statements needs to be exhaustive. In other words, one case will always apply. This requires using a default case.

Another nice syntactical enchantment is the ability to create a comma-separated list of cases that should yield the same value. Clearly, this presents a good and nicely readable code construct, which perfectly reflects the intended purpose of conditionally deriving a value.

Interesting details about switch expressions


Technically speaking, returning a value from a switch expression is not a requirement, and it is also possible to construct more-complex switch expressions that create blocks of code to implement various cases. See Figure 6 and Figure 7.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 6. Example of using code blocks in a switch expression

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 7. Example of using the yield operator to return a value from the code block in a switch expression

Another significant difference between the older switch statement and the newer style of switch expression is that in switch expressions, each case statement has its own scope and may declare local variables independently from other case statements. In the older switch statement, case statements are treated as goto targets that are merely code labels within the same block, and thus all case statements share the same scope.

An additional operator, yield, was introduced in the Java language to help deal with the situation where you’d like to create a switch expression that returns a value and at the same time uses blocks of code to implement cases. In these circumstances, yield must be used to return a value for the relevant case.

As you might imagine, adding this new keyword to an established programming language is an inherently awkward thing to do because of its potential of breaking existing code that might have used the keyword yield as an identifier, for example, as a variable name.

To avoid such confusion, the new yield operator is not considered to be a keyword anywhere in Java except within the switch expression construct. When the switch expression specification was formulated, there was a discussion regarding the use of the break operator instead for this purpose. However, despite some backward compatibility inconveniences, it was decided that using yield to return a value is better than repurposing break, because using break would result in a much more confusing set of syntactical rules and produce less-readable and potentially misleading code.

Pattern matching for switch


Another interesting emerging development related to the switch construct is an introduction of the pattern matching capabilities for switch (see Figure 8).

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 8. Preview of pattern matching with a switch statement

Pattern matching is already a production feature for the instanceof operator. However, for switch it is still a preview feature and is described by JEP 406. Although it is not yet a production feature, this preview feature shows the direction in which switch is evolving.

The idea is to allow a switch statement or switch expression to accept an object and then trigger a specific case statement that matches this object type. Simultaneously, the object reference is cast to the appropriate type, and a locally scoped variable is introduced to the case statement to represent this casted object reference.

Please note that the actual syntax and implementation details for switch pattern matching can change in the future because this feature is not yet in production.

Source: oracle.com

Monday, January 31, 2022

The best HotSpot JVM options and switches for Java 11 through Java 17

The best HotSpot JVM options and switches for Java 11 through Java 17

In this article, you will learn about some of the systems within the OpenJDK HotSpot Java Virtual Machine (HotSpot JVM) and how they can be tuned to best suit your program and its execution environment.

The HotSpot JVM is an amazing and flexible piece of technology. It is available as a binary release for every major operating system and CPU architecture from the tiny Raspberry Pi Zero all the way up to “big iron” servers containing hundreds of CPU cores and terabytes of RAM. With OpenJDK being an open source project, the HotSpot JVM can be compiled for almost any other system—and it can be fine-tuned using options, switches, and flags.

First, here’s some background. The language of the HotSpot JVM is bytecode. At the time of this writing, there are more than 30 programming languages that can be compiled into HotSpot JVM–compatible bytecode, but by far the most popular, with over 8 million developers worldwide, is, of course, Java.

Java source code is compiled into bytecode (as shown in Figure 1), in the form of class files, using the javac compiler. In modern development this is likely abstracted away by build tools such as Maven, Gradle, or an IDE-based compiler.

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Figure 1. Process for compiling bytecode

The bytecode representation of a program is executed by the HotSpot JVM on a virtual stack machine that knows as many as 256 different instructions, and each instruction is identified by an 8-bit numerical opcode; hence, the name bytecode.

The bytecode program is executed by an interpreter that fetches each instruction, pushes its operands onto the stack, and then executes the instruction, removing the operands and leaving the result on the stack, as shown in Figure 2.

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Figure 2. Results on the stack after the interpreter executes the bytecode

The abstraction of program execution from the underlying environment is what gives Java its “Write once, run anywhere” portability advantage. Class files compiled on one architecture can execute on a HotSpot JVM running on a completely different architecture.

If you’re thinking that this abstraction from the underlying hardware comes at a performance cost, you are correct. That’s often where the switches, options, and flags come in.

Just-in-time compilation


How can programs written in portable, feature-rich, high-level languages such as Java challenge the performance of those compiled to architecture-specific native code from lower-level, less-programmer-friendly languages such as C?

The answer is that the HotSpot JVM contains performance-boosting just-in-time (JIT) compilation technology that profiles your program’s execution and selectively optimizes the parts it decides will benefit the most. These are known as your program’s hot spots (hence, the name of the HotSpot JVM), and it does this by compiling them into native code on the fly using knowledge of the underlying system architecture.

The HotSpot JVM contains two JIT compilers, known as C1 (the client compiler) and C2 (the server compiler), which offer different optimization trade-offs.

◉ C1 offers fast, simple optimizations.
◉ C2 offers advanced optimizations that require more profiling and are more expensive to apply.

Ever since the release of JDK 8, the default behavior has been to use both compilers together in a mode called tiered compilation, where C1 provides rapid speed boosts while C2 gathers enough profiling information before making its advanced optimizations. The native code produced is stored in a memory region of the HotSpot JVM called the code cache, as shown in Figure 3.

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Figure 3. The Java compilation process

Taking out the trash


In addition to JIT technology, the HotSpot JVM also includes productivity- and performance-boosting features such as multithreading and automatic memory management with a choice of garbage collection (GC) strategies.

Objects are allocated in a memory region of the HotSpot JVM called the heap, and once those objects are no longer referenced, they can be cleaned up by the garbage collector and the memory they used is reclaimed.

The ergonomic HotSpot JVM


With so much flexibility and dynamic behavior in the HotSpot JVM, you might worry about how to configure it to best match the requirements of your program. Fortunately, for a lot of use cases, you won’t need to do any manual tuning. The HotSpot JVM contains a process called ergonomics that examines the execution environment at startup and chooses some sensible defaults for the GC strategy, heap size, and JIT compilers based on the number of CPU cores and amount of RAM available. The current defaults are

◉ Garbage collector: G1GC
◉ Initial heap: 1/64th of physical memory
◉ Maximum heap: 1/4th of physical memory
◉ JIT compiler: Tiered compilation using both C1 and C2

You can see all of the ergonomic defaults the HotSpot JVM will choose for your environment by using the option -XX:+PrintFlagsFinal and using the grep command to search for ergonomic, as follows:

Copy code snippet
Copied to ClipboardError: Could not CopyCopied to Clipboard

java -XX:+PrintFlagsFinal | grep ergonomic

  intx CICompilerCount             = 4              {product} {ergonomic}
  uint ConcGCThreads               = 2              {product} {ergonomic}
  uint G1ConcRefinementThreads     = 8              {product} {ergonomic}
  size_t G1HeapRegionSize          = 2097152        {product} {ergonomic}
  uintx GCDrainStackTargetSize     = 64             {product} {ergonomic}
  size_t InitialHeapSize           = 526385152      {product} {ergonomic}
  size_t MarkStackSize             = 4194304        {product} {ergonomic}
  size_t MaxHeapSize               = 8403288064     {product} {ergonomic}
  size_t MaxNewSize                = 5041553408     {product} {ergonomic}
  size_t MinHeapDeltaBytes         = 2097152        {product} {ergonomic}
  uintx NonNMethodCodeHeapSize     = 5836300        {pd product} {ergonomic}
  uintx NonProfiledCodeHeapSize    = 122910970      {pd product} {ergonomic}
  uintx ProfiledCodeHeapSize       = 122910970      {pd product} {ergonomic}
  uintx ReservedCodeCacheSize      = 251658240      {pd product} {ergonomic}
  bool SegmentedCodeCache          = true           {product} {ergonomic}
  bool UseCompressedClassPointers  = true           {lp64_product} {ergonomic}
  bool UseCompressedOops           = true           {lp64_product} {ergonomic}
  bool UseG1GC                     = true           {product} {ergonomic}

This output above is from JDK 11 on a machine with 32 GB of RAM, so the initial heap is set to 1/64th of 32 GB (approximately 512 MB) and the maximum heap is 1/4th of 32 GB (8 GB).

Taking control


If you think the default settings chosen by the ergonomics process will not be a good match for your application, you’ll be pleased to know the HotSpot JVM is highly configurable in every area.

There are three main types of options.

◉ Standard: Basic startup options such as -classpath that are common across HotSpot JVM implementations.

◉ -X: Nonstandard options used to configure common properties of the HotSpot JVM such as controlling the maximum heap size (-Xmx); these are not guaranteed to be supported on all HotSpot JVM implementations.

◉ -XX: Advanced options used to configure advanced properties of the HotSpot JVM. According to the documentation, these are subject to change without notice, but the Java team has a well-managed process for removing them.

The -XX options


Many of the -XX options can be further characterized as follows:

Product. These are the most commonly used -XX options.

Experimental. These are options related to experimental features in the HotSpot JVM that may not yet be production-ready. These options allow you to try out new HotSpot JVM features, and they need to be unlocked by specifying the following:

-XX:+UnlockExperimentalVMOptions

For example, the ZGC garbage collector in JDK 11 can be accessed using

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC

Once an experimental feature becomes production-ready, the options that control it are no longer classed as experimental and do not require unlocking. The ZGC collector became a product option in JDK 15.

Manageable. These options can also be set at runtime via the MXBean API or other JDK tools. For example, to show locks held by java.util.concurrent classes in a HotSpot JVM thread dump use

java -XX:+PrintConcurrentLocks

Diagnostic. These options are related to accessing advanced diagnostic information about the HotSpot JVM. These options require you to use the following before they can be used:

-XX:+UnlockDiagnosticVMOptions

An example diagnostic option is

-XX:+LogCompilation

It instructs the HotSpot JVM to output a log file containing details of all the optimizations made by the JIT compilers. You can inspect this output to understand which parts of your program were optimized and to identify parts of your program that might not have been optimized as you expected.

The LogCompilation output is verbose but can be visualized in a tool such as JITWatch, which can tell you about method inlining, escape analysis, lock elision, and other optimizations that the HotSpot JVM made to your running code.

Developmental. These options allow configuration and debugging of the most-advanced HotSpot JVM settings, and they require a special debug HotSpot JVM build before you can access them.

Added and removed options


The addition and removal of option switches follows the arrival or deprecation of major features in the HotSpot JVM. Here are some notable points.

◉ In JDK 9 many of the -XX:+Print... and -XX:+Trace... logging options were removed and replaced by the -Xlog option for controlling the unified logging subsystem introduced by JEP 158.

◉ The option count peaked in JDK 11 at a whopping 1,504 after the addition of options for the experimental ZGC, Epsilon, and Shenandoah garbage collectors.

◉ There was a large drop in JDK 14 with the removal of the Concurrent Mark Sweep (CMS) garbage collector, as discussed in JEP 363.

Figure 4 shows the number of HotSpot JVM options in each version of OpenJDK.

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Figure 4. Total number of options (including product, experimental, diagnostic, and developmental) in each version of OpenJDK

Table 1 shows the HotSpot JVM options that were in OpenJDK 16 that were removed from OpenJDK 17. Table 2 shows the new HotSpot JVM options added to OpenJDK 17.

Table 1. The HotSpot JVM options in OpenJDK 16 that were removed from OpenJDK 17

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Table 2. The new HotSpot JVM options added to OpenJDK 17

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

So long and farewell!


So how does the HotSpot JVM development team manage the removal of options? Since JDK 9, the process for removing -XX options was extended to a three-step process of deprecate, obsolete, and expire to give users plenty of warning that their Java command line may soon need to be updated.

Let’s look at how the HotSpot JVM responds to the -XX:+AggressiveOpts option, which was deprecated in JDK 11, obsoleted in JDK 12, and finally expired in JDK 13.

Deprecated options. These options are supported, but a warning is printed to let you know that support may be removed in the future, for example

./jdk11/bin/java -XX:+AggressiveOpts
OpenJDK 64-Bit Server VM warning: Option AggressiveOpts was deprecated in version 11.0 and will likely be removed in a future release.

Obsolete options. These options have been removed but they are still accepted on the command line. A warning is printed to let you know that these options might not be accepted in the future, for example

./jdk12/bin/java -XX:+AggressiveOpts
OpenJDK 64-Bit Server VM warning: Ignoring option AggressiveOpts; support was removed in 12.0

Expired options. These are deprecated or obsolete options that have an accept_until version less than or equal to the current JDK version. A warning is printed when these options are used in the JDK version in which they expired, for example

./jdk13/bin/java -XX:+AggressiveOpts
OpenJDK 64-Bit Server VM warning: Ignoring option AggressiveOpts; support was removed in 12.0

Total failure. Once you go past the JDK version in which an option was expired, the HotSpot JVM will fail to start when the option is passed and a warning is printed, for example

./jdk14/bin/java -XX:+AggressiveOpts
Unrecognized VM option 'AggressiveOpts'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

Sadly, not every option is retired in this orderly manner.

For example, JDK 9 dropped support for a large number of options when it introduced unified logging and the powerful -Xlog option, which is covered in detail in Nicolai Parlog’s blog. There is also a page on the Java documentation website for converting deprecated logging options to Xlog.

Migrating to a later JDK


So, how can you prepare for migrating your Java command line to a later JDK? Perhaps you’ve inherited a command line full of options you’ve never heard of and you are afraid to touch them for fear of destabilizing your application.

I have created a tool to help you: JaCoLine, the Java Command Line Inspector. You can paste in your command line, choose your target platform, and get an analysis of how your options will work. See Figure 5.

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Figure 5. Analyzing command-line options with JaCoLine

These are a few of my favorite things!


Admit it: You came here seeking the magic turbo button that will supercharge your applications. Well, while there is no one-size-fits-all advice when it comes to HotSpot JVM tuning, there are certainly options I believe will help you better understand the execution of your program and make sensible configuration choices.

The following options are available in JDK 11 and later. I’m choosing these switches because many developers have not moved to later versions of Java. And remember, these are all optional; the HotSpot JVM’s defaults are very good.

First, understand memory usage. Allocating memory in the HotSpot JVM is cheap. Garbage collection cost is the tax that falls due sometime later in the form of execution pauses while the HotSpot JVM cleans up the no-longer-needed objects in the heap.

Understanding the heap allocations made by your code and the resulting GC behavior could be the lowest hanging fruit when it comes to improving application performance and stability, because a mismatch between the heap and GC configuration and your application’s allocation behavior can lead to excessive pauses that interrupt the progress of your application.

The JaCoLine Statistics web page confirms that configuring the heap and GC logging are the most popular options across all the command lines JaCoLine examined.

To configure the heap, consider the answers to the following questions:

◉ What is the maximum expected heap usage under normal conditions?
   ◉ -Xmx sets the maximum heap size, for example, -Xmx8g.
   ◉ -XX:MaxRAMPercentage=n sets the maximum heap as a percentage of total RAM.
◉ How quickly do you expect the heap to reach its maximum size?
   ◉ -Xms sets the initial heap size, for example, -Xms256m.
   ◉ -XX:InitialRAMPercentage=n sets the maximum heap as a percentage of total RAM.
   ◉ If you expect the heap to grow rapidly, you can set the initial heap closer to the maximum heap value.

To deal with OutOfMemory errors, consider how the HotSpot JVM should behave if your application runs out of memory.

◉ -XX:+ExitOnOutOfMemoryError tells the HotSpot JVM to exit on the first OutOfMemory error. This can be useful if the HotSpot JVM will be automatically restarted.
◉ -XX:+HeapDumpOnOutOfMemoryError will help you diagnose memory leaks by dumping the contents of the heap to a file (java_pid.hprof in the working directory).
◉ -XX:HeapDumpPath defines the path for the heap dump.

Second, choose a garbage collector. The G1GC collector will be selected by default by the JDK 11 ergonomics process on most hardware, but it is not the only choice in JDK 11 and later.

Other garbage collectors available are

◉ -XX:+UseSerialGC selects the serial collector, which performs all GC work on a single thread.
◉ -XX:+UseParallelGC selects the parallel (throughput) collector, which can perform compaction using multiple threads.
◉ -XX:+UseConcMarkSweepGC selects the CMS collector. Note that the CMS collector was deprecated in JDK 9 and was removed in JDK 14.
◉ -XX:+UnlockExperimentalVMOptions -XX:+UseZGC selects the ZGC collector (experimental in JDK 11 and a standard feature in JDK 14 and later; thus you won’t need this switch).

Advice on selecting a collector for your application can be found in the HotSpot Virtual Machine Garbage Collection Tuning Guide. That’s the version of the document for JDK 11; if you are on a later version of Java, search for the updated documentation.

To avoid premature promotion, consider whether your application creates short-lived objects at a high allocation rate. That can lead to the premature promotion of short-lived objects to the old-generation heap space, where they will accumulate until a full garbage collection is needed.

◉ -XX:NewSize=n defines the initial size for the young generation.
◉ -XX:MaxNewSize=n defines the maximum size for the young generation.
◉ -XX:MaxTenuringThreshold=n is the maximum number of young-generation collections an object can survive before it is promoted to the old generation.

To log memory usage and GC activity, do the following:

◉ Get a full breakdown of the HotSpot JVM’s memory usage upon exit by using
-XX:+UnlockDiagnosticVMOptions ‑XX:NativeMemoryTracking=summary ‑XX:+PrintNMTStatistics.
◉ Enable GC logging with the following:
   ◉ -Xlog:gc provides basic GC logging.
   ◉ -Xlog:gc* provides verbose GC logging.

Finally, understand how the JIT compilers optimized your code. Once you are happy that your application’s GC pauses are at an acceptable level, you can check that the HotSpot JVM’s JIT compilers are optimizing the parts of your program you think are important for performance.

Enable simple compilation logging, as follows:

◉ -XX:+PrintCompilation prints basic information about each JIT compilation to the console.
◉ -XX:+UnlockDiagnosticVMOptions ‑XX:+PrintCompilation ‑XX:+PrintInlining adds information about method inlining.

Example output:

java -XX:+PrintCompilation
  77    1    3    java.lang.StringLatin1::hashCode (42 bytes)
  78    2    3    java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)
  78    3    3    jdk.internal.misc.Unsafe::getObjectAcquire (7 bytes)
  80    4    3    java.lang.Object:: (1 bytes)
  80    5    3    java.lang.String::isLatin1 (19 bytes)
  80    6    3    java.lang.String::hashCode (49 bytes)

The items in the output are (from left to right) as follows:

Core Java, JVM, Oracle Java Career, Oracle Java Preparation, Oracle Java Guides, Oracle Java Exam Prep

Source: oracle.com