Sunday, February 28, 2021

The Text Blocks

Java 13 introduced Text Blocks as a preview feature. It got enhanced and continued as a preview feature in Java 14 release. Java 15 has made this a permanent feature of the language. 

So what is a text block?

Short answer is it is a multi-line string with special syntax. The purpose of introducing it is to simplify and minimize the code required to create a multi-line strings.

Assume for example, we want to capture this famous last stanza of Robert Frost's poem as a Java String. 

The woods are lovely, dark and deep,   
But I have promises to keep,   
And miles to go before I sleep,   
And miles to go before I sleep.

Below code snippet captures this

        String poemString = "The woods are lovely, dark and deep,\n"
                + "But I have promises to keep,\n"
                + "And miles to go before I sleep,\n"
                + "And miles to go before I sleep.";

But this has multiple strings appended through + operator. Also note the '\n' included at the end of each line to capture the new line into the String. 

Things would get more complicated for lengthy string literals, which is not uncommon with HTMLs,  XMLs & JSONs often getting embedded in code. And not to forget the performance implication of many String appends leading to usage of StringBuilder and StringBuffer for optimization. 

Wouldn't it be better if we can get rid of this and have a simpler syntax to capture such multi-line strings? 

This essentially is what the Text Block feature provides. 

Below snippet shows how to use a Text Block to capture the same String

        String poemTextBlock = """
                The woods are lovely, dark and deep,   
                But I have promises to keep,   
                And miles to go before I sleep,   
                And miles to go before I sleep.
                """;

We start and end a Text Block with three double quotes as in """. Format of the text inside of the Text Block is preserved and there is no need to add additional '\n' for the new lines. 

This will greatly simplify the code when embedding HTML, XML, JSON, SQL, JavaScript or other such code snippet in a string literal. 

The full code is embedded below

public class Main {

    public static void main(String[] args) {
        
        String poemTextBlock = """
                The woods are lovely, dark and deep,   
                But I have promises to keep,   
                And miles to go before I sleep,   
                And miles to go before I sleep.
                """;
        
        String poemString = "The woods are lovely, dark and deep,\n"
                + "But I have promises to keep,\n"
                + "And miles to go before I sleep,\n"
                + "And miles to go before I sleep.";
        
        System.out.println("Text Block: "+poemTextBlock);
        System.out.println("String: "+poemString);

    }
}

Note that there is a minor difference in the values represented by poemString & poemTextBlock in the above code.

Try to figure out that difference... We will uncover that in our next post.


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Text-blocks/The-Text-Blocks

Using binding variable in the expression of if statement

Yes. We can use the binding variable inside of an if expression as long as its assured that when the code flow reaches that expression its assured that the instanceof operator has evaluated to true

Below sample code illustrates this


interface Pet {
    default public String color() {
        return this.color();
    }
}

record Dog(String color) implements Pet {

    public void bark() {
        System.out.println("I bark...");
    }
}

record Cat(String color) implements Pet {

    public void meaw() {
        System.out.println("I meaw...");
    }
}

public class Main {

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (p1 instanceof Dog d && d.color().equals("White") && 
                p2 instanceof Cat c && c.color().equals("White")) {
            d.bark();
            c.meaw();
        }
    }
}

Here we are using the binding variables c & d inside of an if expression. Note that the usage is allowed when used with short-circuit && operator, which makes sure the subsequent expression gets evaluated only when the 1st condition evaluates to true. 


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Instanceof-Binding-Variable/In-if-expression

Binding variable scope with NOT (!) condition

Let us introduce a NOT (!) operator to the if condition and study the scope of the binding variable in this case. 

Full sample code with NOT condition shown below  


interface Pet {
    default public String color() {
        return this.color();
    }

}

record Dog(String color) implements Pet {

    public void bark() {
        System.out.println("I bark...");
    }
}

record Cat(String color) implements Pet {

    public void meaw() {
        System.out.println("I meaw...");
    }
}

public class Main {

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (!(p1 instanceof Dog d)) {
            return;
        }
        
        if (!(p2 instanceof Cat c)) {
            return;
        }

        d.bark();
        c.meaw();
    }
}

This code compiles and executes as expected. 

Here binding variables are not accessible inside of the if block because of the not condition and are accessible outside of the if blocks.  

If you observe the code carefully, we are returning immediately if p1 is not Dog and p2 is not Cat. Code flow reaches beyond the if statements if and only if d is Dog and c is Cat. 

It is this unconditional return statement inside of the if block that makes this code work. Had the main method been like this instead, it will not compile

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (!(p1 instanceof Dog d)) {
            System.out.println("p1 is not Dog");
        }
        
        if (!(p2 instanceof Cat c)) {
            System.out.println("p2 is not Cat");
        }

        d.bark();
        c.meaw();
    }

This code will not compile as the flow would reach d.bark() and c.meaw() even when d and c are not Dog & Cat respectively.

Finally in the below code, we use the binding variable in the else part of the if condition with NOT (!) operator. Here, its assured that d and c are Dog and Cat when the flow reaches their respective else blocks 

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (!(p1 instanceof Dog d)) {
            System.out.println("p1 is not Dog");
        } else {
            d.bark();
        }
        
        if (!(p2 instanceof Cat c)) {
            System.out.println("p2 is not Cat");
        } else {
            c.meaw();
        }
    }

This will compile and execute as expected.

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Instanceof-Binding-Variable/With-NOT-condition

Scoping of binding variables

Binding variables of instanceof operator is visible only inside of the code blocks that are reachable when the instanceof operator evaluates to true. 

Below sample illustrates this.


interface Pet {
    default public String color() {
        return this.color();
    }

}

record Dog(String color) implements Pet {

    public void bark() {
        System.out.println("I bark...");
    }
}

record Cat(String color) implements Pet {

    public void meaw() {
        System.out.println("I meaw...");
    }
}

public class Main {

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (p1 instanceof Dog d) {
            d.bark();
        }

        if (p2 instanceof Cat c) {
            c.meaw();
        }

        d.bark(); 
        c.meaw(); // Will not compile. c & d are not visible beyond their respective if blocks
    }
}

In the above sample, lines c.meaw()  and d.bark() outside of the if statement will not compile, as at this point c & d are not visible. 

Now we will make a few changes to the above code to study the scope of binding variables in-depth. 

First lets try to move the instanceof operator with binding variable outside of the if block. The main method is changed as shown below

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        boolean isDog = p1 instanceof Dog d;
        boolean isCat = p2 instanceof Cat c;

        d.bark();
        c.meaw();

    }

In the code above, it looks like c & d should be visible through the rest of the main() method. But this code also fails to compile. Binding variables c & d are not visible beyond the line containing their respective instanceof operator 

The code block that is reachable, when the instanceof operator evaluates to true is empty and its scope ends with assigning its result to the boolean variable. The code lines d.bark() and c.meaw() gets executed irrespective of if the instance of operator evaluates to true of false 

This explains why the binding variables are not visible beyond their respective instanceof operator statements. Easy!!! Below scenario gets a bit more tricky

Now lets modify the code to use both the instanceof binding inside of an if block, combining the two instance of operator with a logical & operator as shown below

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (p1 instanceof Dog d & p2 instanceof Cat c) {
            d.bark();
            c.meaw();
        }
    }

Phew... the code still doesn't compile. Binding variables c and d are not visible inside of the if block :(

Since the logical AND (&) operator is not short-circuiting and the second condition gets executed even which the first check evaluates to false, the scope of the binding variable is not extended beyond the instanceof operator statement and hence not visible inside of the if block!!! Bit tricky, but thats how the scope of the binding variable is...

And now, lets try with the short circuit AND operator (&&) as shown below

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        if (p1 instanceof Dog d && p2 instanceof Cat c) {
            d.bark();
            c.meaw();
        }
    }

This time, code compiles and executes as expected. Every piece of code beyond the instanceof binding variable assignment gets executed if and only if the instance of operator evaluates to true. This makes the binding variables c and d visible inside of the if block. 

And there is another interesting case on the scope of binding variable when used with the NOT (!) operator. We will explore that in the next post.


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Instanceof-Binding-Variable/Scoping

A complete sample for binding variable usage

Below is a complete sample code illustrating the usage of binding variables with instanceof operator

In the below code, we have an interface Pet which is implemented by two record classes - Cat & Dog. Dog has an instance method bark() and Cat has an instance method meaw().

In the main() method of the Main class, we proceed to create Pet instances for Dog and Cat and proceed to show usage of instanceof operator with & without the usage of binding variable


interface Pet {
    default public String color() {
        return this.color();
    }
}

record Dog(String color) implements Pet {

    public void bark() {
        System.out.println("I bark...");
    }
}

record Cat(String color) implements Pet {

    public void meaw() {
        System.out.println("I meaw...");
    }
}

public class Main {

    public static void main(String[] args) {

        Pet p1 = new Dog("White");
        Pet p2 = new Cat("White");

        // Without using binding variable
        if (p1 instanceof Dog) {
            Dog d = (Dog) p1;
            d.bark();
        }

        // With binding variable
        if (p2 instanceof Cat c) {
            c.meaw();
        }
        
    }
}


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Instanceof-Binding-Variable/A-complete-sample

Friday, February 26, 2021

Binding variable for instanceof operator

We know that instanceof operator tests a variable for a particular type. This is generally used when we have a variable of a super type and we want to check if its of a specific subtype before casting and invoking specific methods or attributes of that subtype

Here is the sample of instanceof usage

    public int getSizeOfString(Object o) {
        if (o instanceof String) {
            String s = (String) o;
            return s.length();
        } else {
            return 1;
        }
    }

We check if o is of type String. If so, we cast it to String, assign it to the variable s and then proceed to return the length of the string. Otherwise we return 1 as the default size.

Binding variable for instanceof operator introduced in Java 15, makes this casting and assignment to the local variable much more concise and less error prone. 

Below is this code using binding variable with instanceof operator

    public int getSizeOfString(Object o) {
        if (o instanceof String s) {
            return s.length();
        } else {
            return 1;
        }
    }

In the above code, s is the binding variable used with the instanceof operator. If Object o is of type String, it is cast to String and assigned to s as part of the single line statement. We can proceed to use s as a String variable holding the object assigned inside of the if block. 

As you can see, not only does this make the code concise, it also eliminates the possibility for errors due to wrong manual casting.

We will explore more scenarios of using binding variable with instanceof operator in the next few posts. 

A complete sample for binding variable usage

Scoping of binding variables

Binding variable scope with NOT (!) condition

Using binding variable in the expression of if statement


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Instanceof-Binding-Variable/Usage

Tuesday, February 23, 2021

Reflection support for record classes

Two new methods are added to java.lang.Class to examine if a class or a Record class and if so, to find out its components. 

These two methods are 

1. isRecord - which returns a boolean indicating if the class is a record or not

2. getRecordComponents - which returns an array of java.lang.reflect.RecordComponent objects with each element representing a component of the record in the same order

Below sample code demonstrates the usage of these two methods

import java.lang.reflect.RecordComponent;

class Dog {
}

record Cat(String color) {
}

record Pair(Cat cat, Dog dog) {
}

class Main {

    public static void main(String[] args) {
        checkRecord(Dog.class);
        checkRecord(Cat.class);
        checkRecord(Pair.class);
    }

    private static void checkRecord(Class classToCheck) {
        System.out.println("\n" + classToCheck.getName() + " is a record class: " + classToCheck.isRecord());
        if (classToCheck.isRecord()) {
            System.out.print("Its components are: ");

            RecordComponent[] rComponents = classToCheck.getRecordComponents();
            for (RecordComponent component : rComponents) {
                System.out.print(component + ", ");
            }
            System.out.println();
        }

    }
}

In the above code, we have one regular class Dog and two record classes - Cat & Pair. 

While Cat has one component - color of type string, Pair has two components - Cat as the first component and Dog as its second component. 

The output of the above program is

Dog is a record class: false

Cat is a record class: true
Its components are: java.lang.String color,

Pair is a record class: true
Its components are: Cat cat, Dog dog,

As expected, for Cat and Pair, isRecord return true and its components gets printed.

java.lang.reflect.RecordComponent is a new class introduced in Java 14 to provide reflection access to record components. API documentation for this class is available at https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/reflect/RecordComponent.html    


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Reflection-support-for-record-classes

Friday, February 19, 2021

Serialization of Record with circular references

There is a difference in the way records gets serialized and de-serialized. The complete spec for record serialization is available at https://docs.oracle.com/en/java/javase/15/docs/specs/records-serialization.html for reference.

Now lets consider one specific case where we have circular reference between objects that are serialized. 

Below code sample illustrates this

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Cat class. Contains a member of type Kitten
 */
class Cat implements Serializable {

    private Kitten child;

    // Constructor
    public Cat(Kitten child) {
        this.child = child;
    }

    // Accessor to get child reference
    public Kitten child() {
        return this.child;
    }

    public String toString() {
        return "Mother@"+this.hashCode();
    }
}

/**
 * Kitten class. Contains a member of type Cat
 */
class Kitten implements Serializable {

    private Cat mother;

    // Accessor to get mother reference
    public Cat mother() {
        return mother;
    }

    // Setter to set mother of the Kitten
    public void setMother(Cat mother) {
        this.mother = mother;
    }

    
    public String toString() {
        return "Child@"+this.hashCode();
    }

}

public class SerializeTest {
    public static void main(String args[]) throws IOException, FileNotFoundException, ClassNotFoundException {
        
        Kitten child = new Kitten(); // Creating a Kitten object
        Cat mother = new Cat(child); // Creating a Cat object, with Kitten object created as its child
        child.setMother(mother); // Set the Cat object created in the previous step as mother for the Kitten object, thus creating circular reference

        System.out.println("****Before****");
        print(mother); // Print the object tree of the mother

        // Serialize and write the mother object to a file
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("out.obj"));
        os.writeObject(mother);
        os.close();

        // De-serialize and read the mother object from the file
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("out.obj"));
        mother = (Cat) in.readObject();

        System.out.println("****After****");
        print(mother); // Print the object tree of the mother after de-serialization
    }

    // Utility method to print the mother object tree
    public static void print(Cat mother) {
        System.out.println("mother is: " + mother);
        System.out.println("mother.child is: " + mother.child());        
        System.out.println("mother.child.mother is: " + mother.child().mother());
    }
}

Here we have a Cat & Kitten classes. Cat has a reference to Kitten & Kitten has a reference to Cat. We create instances of Cat and Kitten and set references so as to create a circular reference. We then print the object tree to see the circular referencing of objects in the object tree. 

Now we write the object to a file thus serializing the object tree. We then read from the file and recreate the object tree thus performing de-serialization. Comments are added in the code snippet above providing details about the logic. Pls. refer the same for additional details.

Note that in this version of code Cat and Kitten are now regular classes. We will change the Cat implementation as a Record and see what happens shortly, but before that lets see how the serialization and deserialization of object tree behaves with regular classes.  

We execute the above code and get the output as shown below

****Before****
mother is: Mother@1325547227
mother.child is: Child@1528902577
mother.child.mother is: Mother@1325547227
****After****
mother is: Mother@584634336
mother.child is: Child@1469821799
mother.child.mother is: Mother@584634336

We see that circular references are preserved after de-serialization. Perfect!!! 

Now lets change the Cat implementation as Record. Below is the excact same code with Cat implementation alone changed as record class instead of a regular class

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Cat class. Contains a member of type Kitten
 */
record Cat(Kitten child) implements Serializable {

    public String toString() {
        return "Mother@"+this.hashCode();
    }
}

/**
 * Kitten class. Contains a member of type Cat
 */
class Kitten implements Serializable {

    private Cat mother;

    // Accessor to get mother reference
    public Cat mother() {
        return mother;
    }

    // Setter to set mother of the Kitten
    public void setMother(Cat mother) {
        this.mother = mother;
    }

    
    public String toString() {
        return "Child@"+this.hashCode();
    }

}

public class SerializeTest {
    public static void main(String args[]) throws IOException, FileNotFoundException, ClassNotFoundException {
        
        Kitten child = new Kitten(); // Creating a Kitten object
        Cat mother = new Cat(child); // Creating a Cat object, with Kitten object created as its child
        child.setMother(mother); // Set the Cat object created in the previous step as mother for the Kitten object, thus creating circular reference

        System.out.println("****Before****");
        print(mother); // Print the object tree of the mother

        // Serialize and write the mother object to a file
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("out.obj"));
        os.writeObject(mother);
        os.close();

        // De-serialize and read the mother object from the file
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("out.obj"));
        mother = (Cat) in.readObject();

        System.out.println("****After****");
        print(mother); // Print the object tree of the mother after de-serialization
    }

    // Utility method to print the mother object tree
    public static void print(Cat mother) {
        System.out.println("mother is: " + mother);
        System.out.println("mother.child is: " + mother.child());        
        System.out.println("mother.child.mother is: " + mother.child().mother());
    }
}

See how succinct and void of ceremonies the Cat implementation is as Record...

Now we execute this code and the output is...

****Before****
mother is: Mother@764977973
mother.child is: Child@764977973
mother.child.mother is: Mother@764977973
****After****
mother is: Mother@1811075214
mother.child is: Child@1811075214
mother.child.mother is: null

Before image object tree is the same as that with the regular classes. 

But after serialization and de-serialization, we see that circular reference is broken and the default value for object - 'null' is set instead for the child objects mother reference. 

This is because of the way a Record objects gets de-serialized:

  1. First all the component fields of the Record gets created - reading from the stream data
  2. And then Record object is created by calling the Record class canonical constructor  

When the first step is performed in our example, Kitten (child) object gets created and since at this time the Cat (mother) record object is not yet created, it set to the default null value. 

Then in the second step, Cat (mother) object is created with the Kitten (child) object created in the 1st step as its component. This Kitten (child) object has its mother set to null which explains the output above. 

For full technical details on serialization & de-serialization of record classes, see the spec link mentioned at the top of this post 


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Serialization-of-Record-with-circular-references

Compact constructor for Record classes

Consider a scenario where we wanted to validate the parameter before constructing the object. 

In our Cat record class example, we wanted to validate the color parameter before allowed an instance of Cat to be created. 

We can have a canonical constructor for our Cat record, in which we include the validation. Code for this shown below

record Cat(String color) {
    Cat(String color) {
        if (color.matches(".*\\d.*")) {
            throw new IllegalArgumentException("Invalid color");
        }
        this.color = color;
    }
}

Here, we validate the color parameter in the constructor to check if it contains any numeric character in it. 

And if it does, we throw an IllegalArgumentException. Otherwise we proceed to complete the creation of the object by assigning the color to auto-generated instance variable.

Note however that there is repetition of the parameter list - one at the record header and again the same list is repeated in the constructor. 

This can be avoided by using a compact form of constructor declaration supported for record classes. 

Below is the same code with compact constructor

record Cat(String color) {
    Cat {
        if (color.matches("*\\d*")) {
            throw new IllegalArgumentException("Invalid color");
        }
    }
}

The parameter list for compact constructor is implicit, and is the same as that of component list in record definition. 

Also note the missing assignment statement in the compact constructor form

this.color = color;

This again is implicit, all implicit parameters gets assigned to their corresponding instance variables. 

Another question is at what point in time is this assignment done? 

Well, its done at the end of the constructor, after all the custom validation code in the compact constructor is executed. To illustrate this, consider the following code 

record Cat(String color) {
    Cat {
        if (this.color.matches("*\\d*")) {
            throw new IllegalArgumentException("Invalid color");
        }
    }
}

Here we try to use this.color in our validation logic. 

This code fails to compile, throwing an error "variable color might not have been initialized", indicating that the parameter assignment to instance variables happens at the end of construtor.


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Compact-constructor

Wednesday, February 17, 2021

More restrictions with using record classes

Instance Variables not allowed

A record class cannot have instance variables defined in its body. 

The components in the record header gets converted to instance variables in its generated class. But we cannot declare an instance variable explicitly - either matching the one defined in the header or a completely new one. 

The below code will not compile

record Cat(String color) {
    
    // Not allowed - Declaring the instance variable for a record component explicitly
    private final String color; 

    // Not allowed - Declaring an instance variable not listed in the record header
    private final String breed; 

}

This code throws a compilation error stating "field declaration must be static" and a message "(consider replacing field with record component)"

So, the only set of instance variables that we can have in a record class is the components included in its header definition.

Instance initialization blocks not allowed

Also not allowed are instance initialization blocks inside the record body

record Cat(String color) {

    // Not allowed - Having instance initialization block
    {
        System.out.println("Creating an instance");
    }
}

Above code throws an error stating "instance initializers not allowed in records" during compilation.

Native methods not allowed

One more restriction that applies for record classes is that it cannot have any native methods defined in it

record Cat(String color) {
    
    // Not allowed - implementing a native method inside a record
    native void iAmNative();
}

This throws a compilation error "modifier native not allowed here"

Usage of APIs or Classes named "Record"

This is more of a convention to be taken care of when using any API or Class with the name as Record

This stems from the fact that Java language imports all classes in java.lang package using 

import java.lang.*;

With the introduction of java.lang.Record which is the base class of all record classes, the name Record conflicts with the Record class imported from java.lang

For example, lets say we have a our custom class named "Record" defined inside "test" package

package test;

public class Record {

}

If we want to import and use this class, as in the below code

import test.*;

class Main {
    public static void main(String args[]) {
        Record rec = new Record();
    }
}

This would give a compilation error stating "reference to Record is ambiguous"

Import with fully qualified name to get around this problem. The below code makes right reference to the Record class 

import test.Record;

class Main {
    public static void main(String args[]) {
        Record rec = new Record();
    }
}

We will get into exploring some more features of record classes in the next post.


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/More-restrictions

Tuesday, February 16, 2021

Restrictions with record classes

In a previous post, we saw how a record class gets converted to a regular class definition during compilation. Though that conceptually shows how a record class behaves, it doesn't represent the conversion accurately. 

Lets see how exactly a record class is getting converted during compilation. We will use the same simple, single line Cat record definition for this analysis

record Cat(String color) {}

Lets compile this record class using the below command

javac --enable-preview --release 15 Cat.java

This will generate the Cat.class file in the same folder. We will analyze this class using reflection. For this purpose, we will use the below reflect utility - which prints out the basic details about the class to the console.

import java.lang.reflect.Modifier;

public class ReflectClass {

    public static void reflect(Class<?> ref) {
        log("Name",ref.getName());
        log("Module",ref.getModule().getName());
        log("PackageName",ref.getPackageName());
        log("Superclass",ref.getSuperclass().getName());
        log("Modifiers",Modifier.toString(ref.getModifiers()));
        log("DeclaredConstructors", ref.getDeclaredConstructors());
        log("DeclaredFields", ref.getDeclaredFields());
        log("DeclaredMethods", ref.getDeclaredMethods());
        
    }

    static void log(String name, Object[] values) {
        log(String.format("****%s****", name));
        int i = 0;
        for (Object val:values) {
            log(name+"-"+i++, val.toString());
        }
    }

    static void log(String name, String value) {
        log(String.format("%-25s: %s", name, value));
    }

    static void log(String data) {
        System.out.println(data);;
    }
    
    public static void main(String args[]) throws ClassNotFoundException {
        reflect(Class.forName(args[0]));
    }

}

Now lets try to analyze the Cat class using this utility by running the below command 

java --enable-preview ReflectClass Cat

This generates the output shown below

Name                     : Cat
Module                   : null
PackageName              :
Superclass               : java.lang.Record
Modifiers                : final
****DeclaredConstructors****
DeclaredConstructors-0   : Cat(java.lang.String)
****DeclaredFields****
DeclaredFields-0         : private final java.lang.String Cat.color
****DeclaredMethods****
DeclaredMethods-0        : public final boolean Cat.equals(java.lang.Object)
DeclaredMethods-1        : public final java.lang.String Cat.toString()
DeclaredMethods-2        : public final int Cat.hashCode()
DeclaredMethods-3        : public java.lang.String Cat.color()

Key details from the output highlighted in yellow. Inferences from this output are

1. The generated Cat class extends java.lang.Record. All record classes extend this class. It implies that a record class cannot extend any other class

2. Also we see the class generated is final - indicating that this record class cannot be extended by any other class

3. The record components gets converted to private final instance variables and there are not setter methods. The record components are immutable and cannot be changed after initialization. Note however that this immutability is shallow and reference attributes within a component may be mutable

4. A significant difference to note when working records is the deviation from the Java convention that the public accessor method is named color() and not getColor(). The generated accessor method does not follow the getter convention

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Restrictions-with-record-classes


Monday, February 15, 2021

Structure of record definition

In this post, lets look at the structure of a record definition and understand its constituents

A record definition looks like this



Each part of the definition explained below:

  • access specifier - Specifies the scope for the record type. This is optional and if specified should be public. When not given, the record is defined in default scope. 
  • Record keyword - The keyword indicating that this is a record definition
  • Name of the record - The Name for the record type defined
  • Components of the record - Specifies the list of attributes that comprise this record, each defined with its type. This is optional and a record can be defined without any components. This section is also referred to as the record header
  • Body of the record - Block containing the implementation code for the record. It can be left empty to have the default behavior of the record. To add new functionality to the record or to override the default behavior, implementation code should be provided in the body.   

Here is a bare minimum definition for a record

record Cat() {}

We touched upon how the record definition gets converted to a class definition with constructor, accessor and other method implementations during compilation in the previous post. 

Lets dig a bit deeper into this and see what actually happens in the next post.

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Structure-of-record-definition

Sunday, February 14, 2021

Record classes in Java

The main idea behind Record classes introduced in Java 14 as a preview feature and enhanced with additional features in Java 15 is to eliminate ceremonies associated with creating plain data aggregate classes. 

Consider for example a Cat class which has one attribute - a String that represents the color of the Cat. At a bare minimum, we will have an instance variable to hold the color attribute, a constructor to initialize the Cat object and accessor methods for the single instance variable.

Code for this simple Cat implementation is given below

public class Cat {

    private String color;

    public Cat(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

}

Now lets strengthen this class implementation to production grade. We will add the implementation for these 3 methods to our Cat class

1. equals() - which returns true if the two cats compared has the same color

2. hashCode() - which should return the same value for two cat objects if they are equal as determined by the equals() implementation

3. toString() - which returns readable String representation for the Cat object. 

Below is the code with these 3 methods added to the Cat class

import java.util.Objects;

public class Cat {

    private String color;

    public Cat(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        } else if (!(that instanceof Cat)) {
            return false;
        } else {
            Cat thatCat = (Cat) that;
            return Objects.equals(this.color, thatCat.color);
        }
    }

    @Override
    public int hashCode() {
        return this.color.hashCode();
    }

    @Override
    public String toString() {
        return String.format("Cat[color=%s]", this.color);
    }
}

Now we want to make this class immutable. We do this by removing the only setter method in the class definition - the setColor() method.

We also want to make it non-extendable so that no other class inherits from this class. This we achieve of course by making the class final. 

Final class definition with all these changes is:

import java.util.Objects;

public final class Cat {

    private String color;

    public Cat(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        } else if (!(that instanceof Cat)) {
            return false;
        } else {
            Cat thatCat = (Cat) that;
            return Objects.equals(this.color, thatCat.color);
        }
    }

    @Override
    public int hashCode() {
        return this.color.hashCode();
    }

    @Override
    public String toString() {
        return String.format("Cat[color=%s]", this.color);
    }
}

The above code can be replaced by a simple record definition code below

public record Cat(String color) {}

So a record allows us to create plan data aggregation classes through simple definition eliminating the need to create ceremonial code needed with regular class definition. 

And yes... java converts record definition into equivalent class during compilation. We will explore in-depth, the characteristics and behavior of record classes in the next few posts.

Structure of record definition

Restrictions with record classes

More restrictions with using record classes

Compact constructor for Record classes

Serialization of Record with circular references

Reflection support for record classes

 

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Record-classes

Friday, February 12, 2021

Sealed classes and reflection

Two new methods are added to java.lang.Class to examine if a Class or an Interface is a sealed class and if so, to find what are its supported subtypes. 

These two methods are 

1. isSealed - which returns a boolean indicating if the class is sealed or  not

2. permittedSubclasses- which returns an array of ClassDesc representing the supported sub classes

Below sample code demonstrates this

import java.lang.constant.ClassDesc;

public sealed interface Pet {
    
}

final class Cat implements Pet {

}

final class Dog implements Pet {
    
}

class Main {
    public static void main(String[] args) {
        System.out.println("Pet is a sealed class: "+Pet.class.isSealed());
        System.out.print("Its supported subtypes are: ");
        ClassDesc[] cDescArr = Pet.class.permittedSubclasses();
        for (ClassDesc cDesc : cDescArr) {
            System.out.print(cDesc.displayName()+" ");
        }
    }
}

The output of the above program is

Pet is a sealed class: true
Its supported subtypes are: Cat Dog

We will move on to the another new feature in Java 15 - Records in the next post

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Sealed-classes/Reflection

Module and package constraint for permitted sub types

In sealed classes, this constraint apply.

That when you are using modules, the sealed class and its permitted sub types must be defined within the same module. 

If you are not using modules, the sealed class and its permitted sub types must be defined within the same package. 

The below code will not compile unless they are included as part of the same module

package samples.j15.sealed;

import samples.j15.sealed.impl.*;

public sealed interface Pet permits Cat {

}
package samples.j15.sealed.impl;

import samples.j15.sealed.Pet; 

public non-sealed class Cat implements Pet {

}

This is not allowed because the sealed class Pet and its permitted sub type Cat are in different packages - samples.j15.sealed and samples.j15.sealed.impl respectively. 

This code will compile only if these 2 packages are part of the same module. 

To include the above to packages, just create a module-info.java file at the root folder, with only the module name as in the following declaration 

module sealed.samples {
    
}

This will make the two packages part of the sealed.samples module. Now this code will compile because they are now part of the same module.

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Sealed-classes/Module-and-package-constraint

Defining the sealed class and all its permitted types in a single file

Look at the below example in which the sealed interface and all the class that implements it are defined in the same source file

import java.util.Random;

public sealed interface Pet permits Cat, Dog {

    final Random random = new Random();
    
    default Pet myLuckyPet() {
        if(random.nextInt(2) == 0) {
            return new Cat();
        }
        return new Dog();
    }
}

final class Cat implements Pet {

}

final class Dog implements Pet {

}

Here Pet interface is a sealed type and permits only Cat and Dog to inherit from it. 

We define the implementing Cat and Dog classes in the same file. 

We also have a default method in Pet interface which returns a Pet instance (Cat or Dog) randomly. 

Nothing unusual about it. Restrictions of Java applies here as well, that Cat and Dog are package private and can be used only within the same package. If we want the Cat & Dog to be public, they would have to be defined in their own files.

But one advantage of defining a sealed type and its permitted types in the same file is that we can omit the permits clause in the Pet declaration. Java would infer it and include all the implementing types that are defined in the same source file as the set for permits types. 

public sealed interface Pet {

    final Random random = new Random();
    
    default Pet myLuckyPet() {
        if(random.nextInt(2) == 0) {
            return new Cat();
        }
        return new Dog();
    }
}

final class Cat implements Pet {

}

final class Dog implements Pet {

}

This code snippet behaves the same was as the 1st one. When defined in the same file, Java can infer the permitted types and we don't need to provide the permits clause with the list of allowed subtypes.

But when Permits is provided, full list of all permitted types must be specified include types defined in the same file and elsewhere.

Note however that when defining a sealed class without the permits clause, at least one subtype definition should be provided in the same file.  

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Sealed-classes/Single-file

Thursday, February 11, 2021

Sealed Classes

In the next set of posts, lets see some of the features of Java language. 

We will start with sealed classes. This is introduced as a preview feature in Java15. 

So what is a sealed class? 

In java language we can have a final class or a non-final class (class not declared as final). A final class cannot be sub-classed. Non-final class can be extended by any class as long as the base class is visible to it. What if we want to define the entire class hierarchy and not leave it open for any class to extend our class hierarchy in the future?

Lets take a simple example. Lets say we run a pet shop and exclusively deal only with cats & dogs. We want to model this and define a class hierarchy for our case. We will have a base Pet interface, Cat & Dog class both implement the Pet interface. While we want to allow any no. of sub classes for Cat, we want to restrict Dog class to be extended only by the two types of dog we deal with. The hierarchy we want is as shown below




Now we don't want any other class or interface to implement or extend from Pet Interface. Similarly we don't want any other classes other than GermanShepard & LabradorRetriever to extend from Dog class. How can we control this? Sealed classes helps here.

Lets understand some keywords first

sealed: A class can be declared as sealed. It means that this class can be extended only by the set of classes mentioned in the permits clause of its definition

permits: Allows to specify a comma separated set of classes that are permitted to extend from the sealed class

non-sealed: When a class extends from a sealed class, it has to be declared as one of 

  1. final - it cannot be extended any further
  2. sealed - it itself is also sealed, with the set of classes that can extend from it declared in its own permits clause
  3. non-sealed - Specifies that the class hierarchy at this point is no longer sealed and any no. of classes can extend from it

Sample code that implements the type hierarchy shown in the above diagram is given below

public sealed interface Pet permits Cat, Dog {

}

public abstract non-sealed class Cat implements Pet {

}

public abstract sealed class Dog implements Pet permits GermanShepard, LabradorRetriever {

}

public final class GermanShepard extends Dog {

}

public final class LabradorRetriever extends Dog {

}

Here Pet is the interface which allows only Cat & Dog types to implement or extend it.

Cat & Dog are abstract classes. While Cat is non-sealed allowing any class to extend from it, Dog is sealed and allows only GermanShepard & LabradorRetriever classes to extend from it.

GermanShepard & LabradorRetriever are final classes and would prevent any other class to extend from it.

Sealed classes helps impose tight control over type hierarchy that we want to have. Though this feature is commonly referred to as sealed classes, it applies to interfaces as well.

Note: This is a preview feature in Java 15. To compile the above code, use the command with --enable-preview flag as shown below

javac --enable-preview --release 15 *.java

And to run code that uses preview features, run the application with --enable-preview option like

java --enable-preview Main

If you are using Eclipse, enable preview feature by selecting this option


We will get into detailed usage of sealed classes next

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Sealed-classes/Sealed-classes

Tuesday, February 09, 2021

Self restart on OutOfMemoryError

We can use the Java command line option -XX:OnOutOfMemoryError to pass a command to execute when OutOfMemoryError gets thrown. 

We will use the same code we have been using in other exercises, which is

class TestEpsilonGC {

    static final int ALLOCATION_SIZE = 1024 * 1024; // 1MB
    static final int LOOP_COUNT = 12;  
    static long start;

    public static void main(String[] args) {
        start();        
        for (int i = 0; i < LOOP_COUNT; i++) {
            System.out.print("Allocating iteration: "+i);
            byte[] array = new byte[ALLOCATION_SIZE];
            System.out.println(" Done");
        }
        end();
    }
    
    static void start() {
        start = System.currentTimeMillis();
}
    
    static void end() {
            System.out.println("Time taken: "+(System.currentTimeMillis() - start)+"ms");
    }
}

Now create a script file with the command to execute with all the options set. I am running this on my Windows desktop. So using the below command included in a run.bat file

java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError="run.bat" TestEpsilonGC 

 Here we are using run.bat to execute the code, configuring the same script to be executed when OutOfMemoryError is thrown. 

Executing this bat file, gets into an infinite loop of executing the same program again and again. You will have to use Ctrl+C to exit from execution. Sample output from running this program is

>java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError="run.bat" TestEpsilonGC  
[0.008s][info][gc] Using Epsilon
[0.120s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups
[0.164s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 524K (5.12%) used
Allocating iteration: 0[0.180s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 1935K (18.90%) used
 Done
Allocating iteration: 1[0.181s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 2959K (28.90%) used
 Done
Allocating iteration: 2[0.181s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 3983K (38.90%) used
 Done
Allocating iteration: 3[0.183s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 5007K (48.90%) used
 Done
Allocating iteration: 4[0.183s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 6031K (58.90%) used
 Done
Allocating iteration: 5[0.183s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 7055K (68.90%) used
 Done
Allocating iteration: 6[0.183s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 8079K (78.90%) used
 Done
Allocating iteration: 7[0.184s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 9103K (88.90%) used
 Done
Allocating iteration: 8[0.184s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 10127K (98.90%) used
 Done
Allocating iteration: 9java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid22060.hprof ...
Heap dump file created [12675933 bytes in 0.012 secs]
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="run.bat"
#   Executing "run.bat"...

D:\Ashok\Work\Java\gc>java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError="run.bat" TestEpsilonGC  
[0.005s][info][gc] Using Epsilon
[0.005s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups
[0.025s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 524K (5.12%) used
Allocating iteration: 0[0.041s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 1935K (18.90%) used
 Done
Allocating iteration: 1[0.042s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 2959K (28.90%) used
 Done
Allocating iteration: 2[0.042s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 3983K (38.90%) used
 Done
Allocating iteration: 3[0.042s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 5007K (48.90%) used
 Done
Allocating iteration: 4[0.043s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 6031K (58.90%) used
 Done
Allocating iteration: 5[0.043s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 7055K (68.90%) used
 Done
Allocating iteration: 6[0.043s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 8079K (78.90%) used
 Done
Allocating iteration: 7[0.044s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 9103K (88.90%) used
 Done
Allocating iteration: 8[0.044s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 10127K (98.90%) used
 Done
Allocating iteration: 9java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14840.hprof ...
Heap dump file created [12675933 bytes in 0.011 secs]
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="run.bat"
#   Executing "run.bat"...

D:\Ashok\Work\Java\gc>java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError="run.bat" TestEpsilonGC  
[0.005s][info][gc] Using Epsilon
[0.005s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups
[0.025s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 524K (5.12%) used
Allocating iteration: 0[0.042s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 1935K (18.90%) used
 Done
Allocating iteration: 1[0.042s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 2959K (28.90%) used
 Done

As you can see, run.bat gets executed each time the OutOfMemoryError is thrown, effectively restarting it whenever it runs out of memory. 

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Garbage-Collection/EpsilonGC/Self-restart

EpsilonGC and analyzing heap dump generated

Lets try to analyze the heap dump when our test program exits with OutOfMemoryError when run with EpsilonGC configuration. 

Code we use for this is the same test program we used in the previous post, which is 

class TestEpsilonGC {

    static final int ALLOCATION_SIZE = 1024 * 1024; // 1MB
    static final int LOOP_COUNT = 12;  
    static long start;

    public static void main(String[] args) {
        start();        
        for (int i = 0; i < LOOP_COUNT; i++) {
            System.out.print("Allocating iteration: "+i);
            byte[] array = new byte[ALLOCATION_SIZE];
            System.out.println(" Done");
        }
        end();
    }
    
    static void start() {
        start = System.currentTimeMillis();
}
    
    static void end() {
            System.out.println("Time taken: "+(System.currentTimeMillis() - start)+"ms");
    }
}

Now, lets run the below command to create the heap dump when the program exits with OutOfMemoryError

java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError TestEpsilonGC 

This creates the .hprof file in the folder under which you executed the above command. Analyzing it with IBM Heap Analyzer, we find that all objects allocated by the code are listed in the heap dump


In our case, all the byte array objects created before OutOfMemoryError got thrown is listed. 

Remember when analyzing heap dump generated with EpsilonGC configuration, objects occupying the heap space are not necessarily leaked objects. All objects ever created during the run gets listed in the heap dump.

You could use EpsilonGC to 
  • Verify the performance characteristics of your application without any interference of GC overhead
  • Benchmark the heap space to duration of run for your application before it runs out of memory
  • Study all objects getting created and validate it against the design intent of your application
  • You could even choose to use it as final configuration for your application if your application is short lived. In this case having intermittent GC cycles could be undesirable as when application terminates, it will release all the memory
  • Or this could be useful for certain batch processes which are non memory intensive, which can restart and continue where it left off from the previous run 

We could use a simple technique to restart self when OutOfMemoryError is thrown. The next post details how this can be done. 

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Garbage-Collection/EpsilonGC/analyzing-heap-dump

Monday, February 08, 2021

Measuring performance benefits of using EpsilonGC

Lets do a quick experiment to measure the performance benefit of using EpsilonGC.

For this, lets modify the previous program a bit adding in the code for instrumentation. We will use this modified code for our experiment

class TestEpsilonGC {

    static final int ALLOCATION_SIZE = 1024 * 1024; // 1MB
    static final int LOOP_COUNT = 12;  
    static long start;

    public static void main(String[] args) {
        start();        
        for (int i = 0; i < LOOP_COUNT; i++) {
            System.out.print("Allocating iteration: "+i);
            byte[] array = new byte[ALLOCATION_SIZE];
            System.out.println(" Done");
        }
        end();
    }
    
    static void start() {
        start = System.currentTimeMillis();
}
    
    static void end() {
            System.out.println("Time taken: "+(System.currentTimeMillis() - start)+"ms");
    }
}

Lets run this code with default GC now. We will increase the max heap size to 15M so as to allow the program to complete successfully with both the configurations. 

Here is the command and the output

java -verbose:gc -Xmx15M TestEpsilonGC
[0.012s][info][gc] Using G1
Allocating iteration: 0 Done
Allocating iteration: 1 Done
Allocating iteration: 2 Done
Allocating iteration: 3 Done
Allocating iteration: 4[0.075s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 8M->0M(16M) 2.041ms
[0.075s][info][gc] GC(1) Concurrent Cycle
 Done
Allocating iteration: 5 Done
Allocating iteration: 6 Done
Allocating iteration: 7 Done
Allocating iteration: 8 Done
[0.077s][info][gc] GC(1) Pause Remark 10M->10M(16M) 0.578ms
Allocating iteration: 9[0.078s][info][gc] GC(1) Pause Cleanup 12M->12M(16M) 0.108ms
 Done
Allocating iteration: 10[0.078s][info][gc] GC(1) Concurrent Cycle 3.002ms
 Done
Allocating iteration: 11[0.081s][info][gc] GC(2) To-space exhausted
[0.081s][info][gc] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 14M->0M(16M) 2.460ms
[0.081s][info][gc] GC(3) Concurrent Cycle
 Done
[0.083s][info][gc] GC(3) Pause Remark 2M->2M(14M) 0.644ms
[0.084s][info][gc] GC(3) Pause Cleanup 2M->2M(14M) 0.116ms
[0.084s][info][gc] GC(3) Concurrent Cycle 2.742ms
Time taken: 22ms

It has taken about 22ms to complete.

Now lets run this same program with EpsilonGC enabled. Command used and output are

java -verbose:gc -Xmx15M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC TestEpsilonGC
[0.010s][info][gc] Using Epsilon
[0.010s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups
[0.049s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 814K (5.30%) used
Allocating iteration: 0[0.055s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 1919K (12.50%) used
 Done
Allocating iteration: 1[0.056s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 2943K (19.16%) used
 Done
Allocating iteration: 2[0.056s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 3967K (25.83%) used
 Done
Allocating iteration: 3[0.056s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 4991K (32.50%) used
 Done
Allocating iteration: 4[0.057s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 6015K (39.16%) used
 Done
Allocating iteration: 5[0.057s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 7039K (45.83%) used
 Done
Allocating iteration: 6[0.058s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 8063K (52.50%) used
 Done
Allocating iteration: 7[0.058s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 9087K (59.16%) used
 Done
Allocating iteration: 8[0.059s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 10111K (65.83%) used
 Done
Allocating iteration: 9[0.059s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 11135K (72.50%) used
 Done
Allocating iteration: 10[0.060s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 12159K (79.16%) used
 Done
Allocating iteration: 11[0.060s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 13183K (85.83%) used
 Done
Time taken: 13ms
[0.065s][info   ][gc     ] Heap: 15360K reserved, 15360K (100.00%) committed, 13372K (87.06%) used

This has taken just 13ms as against the 22ms taken by the G1 gc configuration. There clearly is a performance advantage in using EpsilonGC. But its use will be limited to very few special cases. 

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Garbage-Collection/EpsilonGC/Measure-of-performance-benefits

 

Testing EpsilonGC

Lets create a small program to test EpsilonGC. 

This program would have to create enough objects to exhaust available heap memory so that the behavior of EpsilonGC can be studied.

Here is a simple program that will help us with this

class TestEpsilonGC {

    static final int ALLOCATION_SIZE = 1024 * 1024; // 1MB
    static final int LOOP_COUNT = 12;  

    public static void main(String[] args) {
        for (int i = 0; i < LOOP_COUNT; i++) {
            System.out.print("Allocating iteration: "+i);
            byte[] array = new byte[ALLOCATION_SIZE];
            System.out.println(" Done");
        }
    }
}

This code creates byte array of 1MB size on each iteration through the loop. 

Now lets try to run this program with default garbage collector and with EpsilonGC enabled, giving it a maximum of 10MB heap space and enabling verbose:gc

Running with default gc configuration.  Here is the command and the output

java -verbose:gc -Xmx10M TestEpsilonGC
[0.032s][info][gc] Using G1
Allocating iteration: 0 Done
Allocating iteration: 1 Done
Allocating iteration: 2[0.114s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 4M->0M(10M) 3.739ms
[0.114s][info][gc] GC(1) Concurrent Cycle
 Done
Allocating iteration: 3 Done
Allocating iteration: 4 Done
Allocating iteration: 5[0.129s][info][gc] GC(1) Pause Remark 8M->8M(10M) 13.647ms
 Done
Allocating iteration: 6[0.130s][info][gc] GC(1) Pause Cleanup 8M->8M(10M) 0.203ms
[0.138s][info][gc] GC(2) To-space exhausted
[0.138s][info][gc] GC(2) Pause Young (Normal) (G1 Humongous Allocation) 8M->0M(10M) 7.786ms
[0.139s][info][gc] GC(1) Concurrent Cycle 24.785ms
 Done
Allocating iteration: 7 Done
Allocating iteration: 8[0.141s][info][gc] GC(3) Pause Young (Concurrent Start) (G1 Humongous Allocation) 4M->0M(10M) 1.592ms
[0.141s][info][gc] GC(4) Concurrent Cycle
 Done
Allocating iteration: 9 Done
Allocating iteration: 10 Done
Allocating iteration: 11 Done
[0.148s][info][gc] GC(4) Pause Remark 8M->8M(10M) 1.111ms
[0.150s][info][gc] GC(4) Pause Cleanup 8M->8M(10M) 0.145ms
[0.151s][info][gc] GC(4) Concurrent Cycle 9.700ms

As you can see, this has used G1 collector, which is the default garbage collector. You can also see that intermittent GC cycles kicking in to reclaim memory. The program ran to completion successfully indicating that the loop completed 12 iterations allocating 1.2MB of objects, using the space reclaimed by the GC.

Now lets see the same code in action with EpsilonGC configuration. 

java -verbose:gc -Xmx10M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC TestEpsilonGC
[0.419s][info][gc] Using Epsilon
[0.425s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups
[0.513s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 524K (5.12%) used
Allocating iteration: 0[0.581s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 1935K (18.90%) used
 Done
Allocating iteration: 1[0.582s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 2959K (28.90%) used
 Done
Allocating iteration: 2[0.583s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 3983K (38.90%) used
 Done
Allocating iteration: 3[0.583s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 5007K (48.90%) used
 Done
Allocating iteration: 4[0.584s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 6031K (58.90%) used
 Done
Allocating iteration: 5[0.585s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 7055K (68.90%) used
 Done
Allocating iteration: 6[0.586s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 8079K (78.90%) used
 Done
Allocating iteration: 7[0.587s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 9103K (88.90%) used
 Done
Allocating iteration: 8[0.588s][info   ][gc     ] Heap: 10240K reserved, 10240K (100.00%) committed, 10127K (98.90%) used
 Done
Allocating iteration: 9Terminating due to java.lang.OutOfMemoryError: Java heap space

This has thrown OutOfMemoryError on the 10th iteration of the loop, indicating that by this time it has used up the heap space and there is not enough space left to continue with the iteration. Instead of GC kicking in, the program has terminated with the OutOfMemoryError.

How performant is EpsilonGC compared to other GC configurations? We will explore that in the next post


Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Garbage-Collection/EpsilonGC/Testing-EpsilonGC


Is there a way to disable garbage collection

Not until recently did I find that Java 11 has introduced a no-op garbage collector, which essentially does nothing. Configuring the Java runtime with this garbage collector essentially disables garbage collection.

Also to be noted is the fact that until Java 11, there is no way to disable garbage collection. That makes sense as for someone like me who have been using Java for the last 20 years, since version 1.2, I was never faced with a situation where there was a need to disable GC. Java does not have an option to deallocate memory. If GC is disabled, who will free up the memory that got allocated through new object creations?

So why now was I looking to disable garbage collection now?

Well, we were into micro-services implementation and experimenting with GraalVM and micro-services frameworks.

Our objective is to spin up services as quickly as possible and replace a running instance when any of our monitored parameters starts showing any sign of weakness.

The obvious problem with Java garbage collection showed its head in our deliberations. If Java stops everything when it does garbage collection, why not 

  • Disable the garbage collection
  • Stop the instance when memory usage goes beyond a threshold
  • Spin up a new instance to handle the low

Sounded by a good option to try

EpsilonGC is the no-ops GC introduced with Java 11. This can be enabled by starting the runtime with configuration flags 

-XX:+UseEpsilonGC

and

-XX:+UnlockExperimentalVMOptions 

Turned out that for our case, having proper GC configured on running instance is a better configuration to have, then to kill it and spin up a new instances frequently. But we are keeping the option of using EpsilonGC open to use when we find a right fit use case.  

We did quiet a few experiments with EpsilonGC. More on that hereherehere & here

Complete EpsilonGC specification can be found at JEP-318