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++);
}
}
}
view raw LoggingSample.java hosted with ❤ by GitHub
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

Effective Java – Third edition​

​​

Effective Java is always a good book to read and this latest edition is no different. Here is the Amazon link to get the book. Here are some code samples and presentation promoting the book. My quick code snippet from the book.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class StringManipLambda {
public static void main(String args[]){
 List stringArras= Arrays.asList("First","Second","third","fourth","fifth");
 Collections.sort(stringArras,(s1,s2)->Integer.compare(s1.length(),s2.length()));
 stringArras.stream().forEach(System.out::println);
    }
}

This is a simple code block, where we take a list of Strings and sort it based on its length. The book goes in depth of how to read this lambda expression and understand how it works.

 

Quickest way of getting an custom error message in Spring Boot 2.x

There are different ways to get a custom error message page in spring boot. The following solution worked for my web project. Here is the overview of the structure.

Pic Pic

To start with, if there is 404 error, then we want a custom error page showing up instead of the spring boot’s default page.
The first thing we are going handle is to suppress the boot framework’s error page. For this, we will go to the application properties and add the following line

server.error.whitelabel.enabled=false

As we are done with that, now let us implement the ErrorController interface. The thing to keep in mind is, we have the LOG entry that says what happened to the code base as we reached here. Here is the sample code.

 

@Controller
public class ITContractErrorController implements ErrorController     {
    public static final Logger LOG = LoggerFactory.getLogger(ITContractErrorController.class);
    @Autowired
    ErrorAttributes errorAttributes;
    @GetMapping({"${server.error.path:${error.path:/error}}"})
    public String handleError() {
        LOG.error("Sever error occured");
        return "/error/error.html";
    }
@Override
public String getErrorPath() {
    return "error";
    }
}

One other way that we can add the custom error message is using the Spring 2.x’s ConfigurableServletWebServerFactory implementation. We add this to the main method of the web module. Here is the sample code for this entry.

 

@SpringBootApplication
public class SkminfycontractorWebmoduleApplication {
    @Bean
    public ConfigurableServletWebServerFactory         containerCustomizer(){
        TomcatServletWebServerFactory factory = new     TomcatServletWebServerFactory();
        factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,     "/error/error.html"));
        return factory;
    }
    public static void main(String[] args) {
        SpringApplication.run(SkminfycontractorWebmoduleApplication.class, args);
    }
}

One thing to remember is, when we implement the override with ConfigurableServeletFactory then Override that is ErrorController is gone.

There is a lot of best practice information laid out in spring documentation and other places. But none worked for my setup. I will update this entry if I find a more interesting solution.

Spring 5 and JUNIT.

Let us try to put together some JUNITs with Spring 5 injection. One of the most challenging things of integrating JUnit and Spring was the role of the IDE. It is important to keep an eye on that.
So here is the code we are going to look.

    @RunWith(SpringJUnit4ClassRunner.class)@ContextHierarchy({
        @ContextConfiguration(classes = StreamsConfig.class)})
        
    public class StreamsFilterSampleTest {
    
      Logger LOG = getLogger(StreamsFilterSampleTest.class);
    
      @Autowired
      StreamsFilterSample streamFilterStream;
      @Autowired
      StreamstoListSample streamstoListSample;

      @Test
      public void testStreamGroupBy() throws Exception {
        LOG.debug("Debugging method start ==> testStreamGroupBy " + "with parameter []");
        streamFilterStream.streamGroupBy();
        LOG.debug("Debugging method end ==> testStreamGroupBy " + "with parameter []");
      }
    }

We see the definition starts with Spring configuration initialization. The annotation

@RunWith(SpringJUnit4ClassRunner.class)@ContextHierarchy({
    @ContextConfiguration(classes = StreamsConfig.class)})

RunWith, we are annotating it to be Spring JUnit. The code block is getting initialized with configuration class of StreamConfig. Now let us take a look at the StreamConfig definition

    @Configuration()
    @ComponentScan("streams")
    public class StreamsConfig {
    public static final Logger LOG = LoggerFactory.getLogger(StreamsConfig.class);
    @Autowired
    StreamsFilterSample streamFilterStream;
      public StreamsConfig() {
        LOG.debug("Constructing Config");
    }
    }

We are autowiring an implementation class, StreamFilterSample. This is just a utility class and code goes something like this.

    public void streamGroupBy() {
    //3 apple, 2 banana, others 1
    List<Item> items = Arrays.asList(
        new Item(null, 10, new BigDecimal("20.00")),
        new Item("apple", 10, new BigDecimal("21.00")),
        new Item("apple", 10, new BigDecimal("22.00")),
        new Item("banana", 20, new BigDecimal("19.99")),
        new Item("orang", 10, new BigDecimal("29.99")),
        new Item("watermelon", 10, new BigDecimal("29.99")),
        new Item("papaya", 20, new BigDecimal("9.99")),
        new Item("apple", 10, new BigDecimal("9.99")),
        new Item("banana", 10, new BigDecimal("19.99")),
        new Item("apple", 20, new BigDecimal("9.99"))
    );
    //group by price
    Map<BigDecimal, List<Item>> groupByPriceMap =
     items.stream().collect(Collectors.groupingBy(Item::getPrice));
    Map<String, List<Item>> groupByNameMap =
        items.stream().filter(item -> item.getName() != null).collect(Collectors.groupingBy
            (Item::getName));
    System.out.println(groupByNameMap);
    }
    private static class Item {
    private String name;
    private int qty;
    private BigDecimal price;
    public Item(String name, int qty, BigDecimal price) {
      this.name = name;
      this.qty = qty;
      this.price = price;
    }
    public Item(String name, BigDecimal price) {this.name = name;this.price = price;}
    public int getQty() {return qty;}
    public void setQty(int qty) {this.qty = qty;}
    public BigDecimal getPrice() {return price;}
    public void setPrice(BigDecimal price) {this.price = price;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    }

Now when we are running the JUnit, we have to make sure the @Test annotation is from the JUnit jar file. If you accidentally have the TestNG’s @Test annotation, it will not be able to understand the configuration that we have in the JUnit header. Finally, remember to make sure the IDE is not making anything goofy where it pulled a wrong Annotation class. Here is a useful Stackoverflow discussion on this.