ZGC options with Java 16

Java 16 provides production ready ZGC garbage collection.

For example here is a sample code that is going to run out of memory with a regular JRE integration, I compared the code run with Java 8 and Java 16. There is an option to log the GC cycles with a VM option, but i felt it was too overwhelming for me to understand, so I took a simpler stats information.

package me.sathish;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class MemorySample {
    public static void main(String[] args) {
        LocalDateTime startTime = LocalDateTime.now();
        System.out.println("Starting time--" + startTime);
        getMemoryStats();
        List<Integer> someList = new ArrayList<>();
        List<Integer> someList1 = new ArrayList<>();
        try {
            for (int i = 0; i < 100000000; i++) {
                someList.add(i);
                someList1.add(i);
            }
        } finally {
            Duration duration = Duration.between(LocalDateTime.now(), startTime);
            getMemoryStats();
            System.out.println("The time diff is --" + duration.getSeconds());
        }
    }

    /**
     * Code barrowed from 
     * https://www.viralpatel.net/getting-jvm-heap-size-used-memory-total-memory-using-java-runtime/
     */
    private static void getMemoryStats() {
        Runtime runtime = Runtime.getRuntime();
        int mb = 1024 * 1024;
        System.out.println("##### Heap utilization statistics [MB] #####");
        //Print free memory
        System.out.println("Used Memory:"
                + (runtime.totalMemory() - runtime.freeMemory()) / mb);
        System.out.println("Free Memory:"
                + runtime.freeMemory() / mb);
        System.out.println("Total Memory:" + runtime.totalMemory() / mb);
        System.out.println("Max Memory:" + runtime.maxMemory() / mb);
    }
}

Now with JAVA 8 the increase the heap VM options to –Xmx4096m– took a total of 18 seconds to complete the above program

##### Start Heap utilization statistics [MB] #####
Used Memory:2
Free Memory:189
Total Memory:192
Max Memory:4096


##### End Heap utilization statistics [MB] #####
Used Memory:3857
Free Memory:238
Total Memory:4096
Max Memory:4096
The time diff is ---18

Enter new JAVA 16 world., with VM options -Xmx4096m -XX:+UseZGC

Total Memory:192
Max Memory:4096
##### Heap utilization statistics [MB] #####
Used Memory:3492
Free Memory:604
Total Memory:4096
Max Memory:4096
The time diff is ---29
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOf(Arrays.java:3511)
	at java.base/java.util.Arrays.copyOf(Arrays.java:3480)
	at java.base/java.util.ArrayList.grow(ArrayList.java:237)
	at java.base/java.util.ArrayList.grow(ArrayList.java:244)
	at java.base/java.util.ArrayList.add(ArrayList.java:454)
	at java.base/java.util.ArrayList.add(ArrayList.java:467)

Another set of options that JAVA 16 gives., new VM options for this run was

-XX:+UseZGC -XX:ZUncommitDelay=10 -XX:MaxHeapSize=10000000024,

A slick 10 seconds to complete everything

##### Start Heap utilization statistics [MB] #####
Used Memory:8
Free Memory:184
Total Memory:192
Max Memory:9538

##### End Heap utilization statistics [MB] #####
Used Memory:7548
Free Memory:0
Total Memory:7548
Max Memory:9538
The time diff is ---10

With JAVA 16 the uncommitDelay can be reduced, so with the following VM options

-XX:+UseZGC -Xmx9538m -XX:ZUncommitDelay=1, the code finished with a 44 second runtime.

Take note of the free memory.

##### Start Heap utilization statistics [MB] #####
Used Memory:8
Free Memory:184
Total Memory:192
Max Memory:9538
##### End Heap utilization statistics [MB] #####
Used Memory:6938
Free Memory:0
Total Memory:6938
Max Memory:9538
The time diff is ---44

So tried one other option with the a cool concurrent GC thread option so the new VM options are

XX:MaxHeapSize=10000000024 -XX:+UseParallelGC -XX:ConcGCThreads=4 -XX:ZUncommitDelay=1

The output for the VM options. The code took the time to complete, but the amount of free memory was around full 1038MB

##### Start Heap utilization statistics [MB] #####
Used Memory:4
Free Memory:179
Total Memory:184
Max Memory:8478
##### End Heap utilization statistics [MB] #####
Used Memory:3862
Free Memory:1038
Total Memory:4901
Max Memory:8478
The time diff is ---115

finally, one last VM options and the output, with a balance of memory usage

-XX:+UseZGC -XX:MaxHeapSize=5126M -XX:ZUncommitDelay=1

##### Start Heap utilization statistics [MB] #####
Used Memory:8
Free Memory:184
Total Memory:192
Max Memory:5126
##### End Heap utilization statistics [MB] #####
Used Memory:4674
Free Memory:452
Total Memory:5126
Max Memory:5126
The time diff is ---52

So nice production ready options with JAVA 16 to work with the new ZGC GC model.

Finally always a great watch.,

Featured

Java 16 logging

One of the features I liked in Java16 are the logging changes. The updated logging API works on handlers. There are different handlers FileHandler, MemoryHandler and ConsoleHandler. Depending on the need, these handlers can be configured in runtime.

Here is a scenario., if you want to debug or pipe entry of the logging information going to a different stream based on the condition. You can configure this in runtime.

Here is a run down code where a log file will get created in runtime based on a condition.

package me.sathish;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggingSample {
private static Logger logger = Logger.getLogger("me.sathish.package");
private static FileHandler fh;
private static FileHandler ch;
static {
try {
fh = new FileHandler("mylog.txt");
ch = new FileHandler("Secondfile.txt");
} catch (IOException e) {
logger.log(Level.SEVERE, "Logging file is not found");
}
}
public static void main(String[] args) {
int start = 5;
int counter = 0;
logger.addHandler(fh);
logger.setLevel(Level.ALL);
for (int i = 0; i < start; i++) {
if (i == 3) {
logger.addHandler(ch);
logger.setLevel(Level.ALL);
}
logger.log(Level.SEVERE, "This is the counter " + counter++);
}
}
}
Logging Gist

In the code block we have a condition where a file handler is added when the counter reaches a value of 3. So now based on this condition we will have two file handlers that will get generated. The first one will have all the entries of the code.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2021-08-28T04:37:27.363135Z</date>
<millis>1630125447363</millis>
<nanos>135000</nanos>
<sequence>0</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter0</message>
</record>
<record>
<date>2021-08-28T04:37:27.412285Z</date>
<millis>1630125447412</millis>
<nanos>285000</nanos>
<sequence>1</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter1</message>
</record>
<record>
<date>2021-08-28T04:37:27.413234Z</date>
<millis>1630125447413</millis>
<nanos>234000</nanos>
<sequence>2</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter2</message>
</record>
<record>
<date>2021-08-28T04:37:27.414026Z</date>
<millis>1630125447414</millis>
<nanos>26000</nanos>
<sequence>3</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter3</message>
</record>
<record>
<date>2021-08-28T04:37:27.414967Z</date>
<millis>1630125447414</millis>
<nanos>967000</nanos>
<sequence>4</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter4</message>
</record>
</log>
view raw mylog.txt hosted with ❤ by GitHub

The second file has only the entries that are satisfying the condition.,

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2021-08-28T04:37:53.555130Z</date>
<millis>1630125473555</millis>
<nanos>130000</nanos>
<sequence>3</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter 3</message>
</record>
<record>
<date>2021-08-28T04:37:53.555951Z</date>
<millis>1630125473555</millis>
<nanos>951000</nanos>
<sequence>4</sequence>
<logger>me.sathish.package</logger>
<level>SEVERE</level>
<class>me.sathish.LoggingSample</class>
<method>main</method>
<thread>1</thread>
<message>This is the counter 4</message>
</record>
</log>
view raw Secondfile.txt hosted with ❤ by GitHub

JAVA 16 and some Spring Boot lessons

Some interesting observations when upgrading boot to JAVA 16. I use IntelliJ for my development.

  • Make sure the pom file is updated with latest JDK version

<properties>
<java.version>16</java.version>
</properties>

  • Boot version that was compatible with 2.4.4. Upgrade of boot prevents this following error

Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet: file [… HiringServiceImplTest.class]; nested exception is java.lang.IllegalArgumentException: Unsupported class file major version 60
Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 60

  • Updated with new version of Boot 2.4.4.<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.4</version>
    <relativePath/> <!-- lookup parent from repository -->

Branch for this changes can be found here

https://github.com/sathishjayapal/hireme.git

Featured

The new COBOL program – Cloud formation

The new COBOL program is the cloud formation template, it does not matter that it is an YAML/JSON.

Few years ago, the main drive for bringing JAVA/.NET framework into the shop was to de couple the giant multi thousand line COBOL programs. These programs had a library of key words. This could be used and the main logic for those programs were based of specific Boolean flags or at least the COBOL programs that I migrated were of this kind of as such.

Fast forward to my new world of writing cloud formation templates, these are the next set of monoliths that are a few thousand lines. The few keywords/logics that can be written are called intrinsic function.

There are different sections in a COBOL program, the same applies to a cloud formation template, for example you have a program, divisions, section etc. The same parallels can be applied to a cloud formation template, there is a section for declaring parameters, conditions, resources and the output.

The next item in a cloud formation are the conditions., this is the section where the parameters passed to the cloud formation templates are evaluated, so that right resources can be provisioned. If you have seen a COBOL program flag logic, this section is the same.

The resources section of the cloud formation template uses the conditions that you defined earlier to provision the resources that is needed.

The output section of the cloud formation stitches all these above sections so that there is an user friendly output.

All these sections are closely tied together, if one fails or errs out, then the entire stack is a dud.

Thinking about doing a cloud formation template, as an engineer it is a challenging feeling to support this development practice. Is this the correct thing to do for the modern world? Cheers.