dcsimg

Java 9 Core Code Examples Part 2

As promised, I am back with more code samples from my previous article covering Java SE 9 This article provides the concluding code examples for each of the new features released in Java SE 9.

You can read the previous parts in this series by visiting:

More Core Library Changes in Java SE 9

Stack-Walking API

Prior to Java 9, the ways to access Stack Trace was very limited and provided the entire dump or stack information all at once. This was inefficient and did not allow any direct way of filtering the data. With the release of Java 9, a lazy StackWalker API was introduced. This allowed fetching data based on filtering conditions and was more efficient. Here is an example:

  package com.techilashots.java9.features;   
  import java.lang.StackWalker.Option;   
  import java.util.List;   
  import java.util.stream.Collectors;   
   
  /**   
  * Stack Walker API is a new feature of Java 9, aimed at Improving Performance of the predecessor Stack Track Element,   
  * as also for providing a way to filter the Stack Elements, in case of Exception or to Understand Application Behavior.    
  * Although, there have been multiple changes, I am covering only Stack Frame Attributes and also the walk() method    
  * for Walking the Stack Frame.    
  */   
  public class StackWalkingService {   
    
    private int databaseService() {   
   
       int x = 3;   
   
       // Usage 01: Walking All Stack Frames   
       System.out.println("Java 9 Stack Walker API - Showing All Stack Frames");   
       StackWalker stackWalker = StackWalker.getInstance();   
       stackWalker.forEach(System.out::println);   
       System.out.println("");   
   
       // Usage 02 : Filtering or Walking Stack Frames   
       System.out.println("Java 9 Stack Walker API - Walking / Filtering Stack Frames");   
       List<StackWalker.StackFrame> stackFrames;   
       stackFrames = stackWalker.walk(frames -> frames.limit(2).collect(Collectors.toList()));   
       stackFrames.forEach(System.out::println);   
       System.out.println("");   
   
       // Usage 03 : Show All Attributes of a Stack Frame   
       System.out.println("Java 9 Stack Walker API - Show All Attributes in Stack Frame");   
       StackWalker newWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);   
       stackFrames = newWalker.walk(frames -> frames.limit(1).collect(Collectors.toList()));   
       stackFrames.forEach(sfRecord->    
       {   
         System.out.printf("[Bytecode Index] %d%n", sfRecord.getByteCodeIndex());   
         System.out.printf("[Class Name] %s%n", sfRecord.getClassName());   
         System.out.printf("[Declaring Class] %s%n", sfRecord.getDeclaringClass());   
         System.out.printf("[File Name] %s%n", sfRecord.getFileName());   
         System.out.printf("[Method Name] %s%n", sfRecord.getFileName());   
         System.out.printf("[Is Native] %b%n", sfRecord.isNativeMethod());   
         System.out.printf("[Line Number] %d%n", sfRecord.getLineNumber());   
       });   
       return x;   
    }   
   
    private float persistenceService() {   
       float x = databaseService();   
       return x;   
    }   
   
    private double businessService() {   
       double x = persistenceService();   
       return x;   
    }   
   
    private double presentationService() {   
       long x = (long) businessService();   
       return x;   
    }   
   
    public void uiDisplay() {   
       System.out.println("Java 9 Stack Walker API for Debugging and Application Behavior");   
       double x = presentationService();   
       System.out.println("\n[Method to Display On User Interface]");   
       System.out.println("Old MacDonald had a Farm. In that Farm, He had " + x + " Cows!");   
    }   
   
    /**   
     * @param args   
     */   
    public static void main(String[] args) {   
       StackWalkingService stackWalkingService = new StackWalkingService();   
       stackWalkingService.uiDisplay();   
    }   
  }   

Compact Strings in Java

Although this has no external ramification to a developer in terms of syntax or semantics changes, It may impact the way we design for memory and performance. The current UTF-16 representation uses 2 Bytes for storage. Most strings contain characters that are only Latin-1 in nature.

The Latin-1 characters require only 1 Byte for Storage. With Java 9, string storage was modified to start with an additional encoding flag. This flag indicated whether strings contained ISO-8859-1/Latin-1 characters or UTF-16 characters. As per the official word, it has to lead to an improved usage of memory and efficient GC, but with some loss in performance at peak loads.

Compact strings are always enabled in Java 9, but it they can be disabled by passing +XX:-CompactStrings in the VM argument.

It has to be noted that in Java 9, the implementation of java.lang.String decides at runtime, whether the storage size is to be 2 Bytes or 1 Byte, as per the actual string size (UTF-16 or Latin-1 Character).

Spin-Wait Hints

While developing multi-threading applications, the addition of Spin-Wait hints brings in some performance improvements under Spin-Waiting or Busy-Waiting conditions. Usually, Busy-Waiting is used for the synchronization of some state of the object between two or more simultaneous threads. This is done while waiting for a condition to occur before processing starts or while processing needs to resume. Thread classes have a new static method known as onSpinWait() that can be optionally called in Busy-Waiting loops. This allows the JVM to issue processor instructions on system architectures to improve reaction time in Spin-Wait or Busy-Waiting loops. It also helps to reduce power consumed by the core thread or the underlying hardware thread. This also benefits the overall power consumption of a program. It does this by allowing other cores or underlying hardware threads to execute at even faster speeds within the same power consumption envelope.

  package com.techilashots.java9.features;   
   
  import java.util.List;   
  import java.util.Vector;   
   
  /**   
  * For a Single Line Demonstration, I wrote the Non-Pure Threaded form of Producer-Consumer. Run the Code Multiple Times   
  * On a Machine, where you can Understand how the Temperature Changes and Extra Cooling Fan Kicks Off. Even though I did   
  * not do it myself, Take it up as an Experiment, by removing the Thread.onSpinWait() [Compare Before/After and also try   
  * with Data Set of 1 Million to 10 Million]   
  *
  * Thread.onSpinWait() will Optimize Latency and Reduce/Optimize Power Consumption   
  */   
  public class SpinWaitHints {   
   
    public static void main(String[] args) {   
       List itemQueue = new Vector();   
       Producer producer = new Producer(itemQueue);   
       Consumer consumer = new Consumer(itemQueue);   
       producer.start();   
       consumer.start();   
    }   
  }   
  class Producer extends Thread {   
   
    List itemQueue;   
    Producer(List itemQueue) {   
       this.itemQueue = itemQueue;   
    }   
    public void run() {   
       try {   
         produce();   
       } catch (InterruptedException e) {   
         e.printStackTrace();   
       }   
    }   
    public void produce() throws InterruptedException {   
       while (true) {   
         while (itemQueue.size() < 100000) // produce 1 lac items   
         {   
            String item = "Sumith Puri " + (itemQueue.size());   
            itemQueue.add(item);   
            System.out.println("Item Produced: " + item);   
         }   
         while (itemQueue.size() > 0) {   
            // spin waiting - x86 architectures will now optimize   
            Thread.onSpinWait();   
         }   
       }   
    }   
  }   
   
  class Consumer extends Thread {   
    List itemQueue;   
    public Consumer(List itemQueue) {   
       this.itemQueue = itemQueue;   
    }   
    public void consume() throws InterruptedException {   
       while (true) {   
         while (itemQueue.size() < 100000) {   
            // spin waiting - x86 architectures will now optimize   
            Thread.onSpinWait();   
         }   
         int x = itemQueue.size();   
         while (x >= 1) {   
            x = x - 1;   
            if (x >= 0) {   
              String item = itemQueue.remove(x);   
              System.out.println("Item Consumed: " + item);   
            }   
         }   
         if (itemQueue.size() > 0)   
            itemQueue.remove(0);   
       }   
    }   
   
    public void run() {   
       try {   
         consume();   
       } catch (InterruptedException e) {   
         e.printStackTrace();   
       }   
    }   
  }   


 

New Version-String Scheme

From Java SE 9 forward, $MAJOR.$MINOR.$SECURITY.$PATCH is the release naming scheme for releases in Java. These details are contained in the Runtime.Version Class. Here is an example of this in use:

  package com.techilashots.java9.features;   
  /**   
  * $MAJOR.$MINOR.$SECURITY+$BUILD is the Naming Scheme for Version String in Java.    
  */   
  public class JavaVersionStringChanges {   
    public void printVersionInformation() {   
       Runtime.Version versionInfo = Runtime.version();   
       System.out.println("Version String Changes in Java 9");   
       System.out.println("Major Version: " + versionInfo.major());   
       System.out.println("Minor Version: " + versionInfo.minor());   
       System.out.println("Security Version: " + versionInfo.security());   
       System.out.println("Build Version: " + versionInfo.build());   
       System.out.println("\nIn Java 9, Version Naming is Major.Minor.Security.Build");   
       System.out.println("Currently Running in Java Version: " + versionInfo.toString());   
    }   
    public static void main(String[] args) {   
       new JavaVersionStringChanges().printVersionInformation();   
    }   
  }   

Enhanced Method Handles

A typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values is called a method handle. These transformations are quite general and include such patterns as insertion, conversion, substitution and deletion. Starting with Java SE 9, method handles have been enhanced to include static methods for creating different kinds of method handles.

Here is the sample code:

  package com.techilashots.java9.features;   
  import java.lang.invoke.MethodHandle;   
  import java.lang.invoke.MethodHandles;   
   
  /**   
  * MethodHandles were introduced first in Java 7. You have to think them as an alternative for Java Reflection API, but   
  * with an advantage of better performance as they are specified at creation time. Enhanced Method Handles has primarily   
  * added new static methods to better/widen the functionality provided by Method Handles.   
  *    
  * Note that Method Handles are Enhanced in Java 9, to introduce very many changes and methods. I will be covering the   
  * ones that are the most important, only to introduce this topic.    
  *    
  * arrayLength, arrayConstructor, zero, empty,    
  * loop, countedloop, iteratedloop, dowhileloop, whileloop, try/finally   
  */   
  public class EnhancedMethodHandles {   
   
    public void enhancedMethodHandleDemo() {   
   
       try {   
    
         // arrayLenth   
         MethodHandle methodHandleLength = MethodHandles.arrayLength(int[].class);   
         int[] ageArray = new int[] { 21, 28, 36 };   
         int arrayLength;   
         arrayLength = (int) methodHandleLength.invoke(ageArray);   
         System.out.println("Length of Array using Method Handle is " + arrayLength);   
           
         // arrayConstructor   
         MethodHandle methodHandleConstructor = MethodHandles.arrayConstructor(int[].class);   
         int[] newAgeArray = (int[]) methodHandleConstructor.invoke(3);   
         System.out.println("Array Constructed using Method Handle of Size " + newAgeArray.length);   
           
         // zero   
         int x = (int) MethodHandles.zero(int.class).invoke();   
         System.out.println("Default Value of Primitive Integer using Method Handles is " + x);   
         String y = (String) MethodHandles.zero(String.class).invoke();   
         System.out.println("Default Value of String using Method Handles is " + y);   
           
         System.out.println();   
         System.out.println("Reader/Developer - Left as an Exercise for You :-)");   
         System.out.println("Refer Loop, CountedLoop, DoWhileLoop, WhileLoop, IteratedLoop, TryFinally");   
       } catch (Throwable e) {   
           
         System.out.println("Was Hungry as Ever - Gulped Everything I Got!");   
       }   
       // refer to, https://goo.gl/JCyo7N (official javadoc)   
       // also use, https://goo.gl/i8wNJ8 (individual blog)   
    }   
   
    public static void main(String[] args) {   
       new EnhancedMethodHandles().enhancedMethodHandleDemo();   
    }   
  }   


 

Variable Handles

Concurrent packages in Java (java.util.concurrent.atomic) provide atomic types for performing atomic operations. Aside from this, unsafe operations (sun.misc.unsafe), such as creating objects without calling the constructor, used in Java low-level programming, require them to be hidden from the outside world.

This requirement has led to the creation of a new abstract class type named VarHandle. This allows a developer to assign different types to the same reference or dynamically typed references. This takes care of performing atomic operations on the held variable, including compare and swap (i.e.; set or exchange) operations. It also provides memory fencing operations, to order the in-memory representation of the object, by providing finer grain control.

Before we go further, you need to understand memory ordering effects, as VarHandle is entirely based on the understanding of plain, volatile, opaque, and acquire/release memory ordering modes.

 package com.techilashots.java9.features;   
   
  import java.lang.invoke.MethodHandles;   
  import java.lang.invoke.VarHandle;   
   
  /**   
  * VarHandle allows developers to assign different types to the same reference (dynamically typed reference).It can also    
  * take care of performing atomic operations on the held variable, including compare and swap (set/exchange) operations.    
  * It also provides memory fencing operations, to order the in-memory representation of the object, by providing finer    
  * grain control.    
  *    
  * I am providing an Example of the Read Operations using VarHandle. Please refer to the link provided below for further   
  * info on VarHandle on Public Variable, VarHandle for Private Variables, VarHandle for Array Types    
  *    
  * https://www.baeldung.com/java-variable-handles   
  */   
  class VarHandleStore {   
   
    public int varIntHandle01 = 5;   
    public int varIntHandle02 = 9;   
    public byte varByteHandle03 = 21;   
  }   
   
  public class VariableHandles {   
   
    public void useVariableHandle() {   
   
       System.out.println("Java 9 Introduces.... Variable Handles!");   
   
       try {   
   
         VarHandleStore varHandleStore = new VarHandleStore();   
         VarHandle varHandle = MethodHandles.lookup().in(VarHandleStore.class).findVarHandle(VarHandleStore.class,   
              "varIntHandle01", int.class);   
         // value using get() in varhandle   
         int plainValue = (int) varHandle.get(varHandleStore);   
         System.out.println("Value using get() in VarHandle: " + plainValue);   
   
         // value is written to using set() - plain access   
         // you can also use set(), setOpaque(), setVolatile(), setRelease()   
         varHandle.set(varHandleStore, 21);   
         plainValue = (int) varHandle.get(varHandleStore);   
         System.out.println("Set Value using set(), then get() in VarHandle: " + plainValue);   
           
         // value is written to using getandadd()   
         int oldValue = (int) varHandle.getAndAdd(varHandleStore, 51);   
         plainValue = (int) varHandle.get(varHandleStore);   
         System.out.println("Using getAndAdd() in VarHandle, Old Value: " + oldValue + ", New Value: " + plainValue);   
         varHandle = MethodHandles.lookup().in(VarHandleStore.class).findVarHandle(VarHandleStore.class,   
              "varIntHandle02", int.class);   
           
         // please do try out the compareandset() - atomic updates   
         // have left this due to time constraints   
         // value is written to using getandbitwiseor()   
         varHandle = MethodHandles.lookup().in(VarHandleStore.class).findVarHandle(VarHandleStore.class,   
              "varByteHandle03", byte.class);   
         byte before = (byte) varHandle.getAndBitwiseOr(varHandleStore, (byte) 127);   
         byte after = (byte) varHandle.get(varHandleStore);   
           
         System.out.println("Get Byte Value, Then Or, using getAndBitwiseOr()");   
         System.out.println("Old Byte Value: " + before + "; New Byte Value: " + after);   
       } catch (NoSuchFieldException | IllegalAccessException e) {   
    
         e.printStackTrace();   
       }   
    }   
   
    public static void main(String[] args) {   
       new VariableHandles().useVariableHandle();   
    }   
  }   


 

Filter Incoming Serialization Data

This feature is related to the addition of filters for the serialization incoming streams to improve security and robustness. The core mechanism is a filter interface implemented by serialization clients and set on ObjectInputStream.

The filter interface methods are called during the deserialization process to validate the following, as the stream is being decoded:

1. Classes being deserialized, 2. The sizes of arrays being created, 3. Metrics describing stream length, 4. Stream depth 5. Number of references

The filter returns a status to accept, reject, or leave the status undecided. The core mechanism is a filter interface implemented by serialization clients, ObjectInputFilter and set on an ObjectInputStream. The filter interface methods are called during the deserialization process to validate the classes being deserialized, The sizes of arrays, the stream length, graph depth and number of references are being created as the stream is being decoded.

A filter determines whether the arguments are rejected or allowed and should return the appropriate status. If the filter cannot determine the status it should return undecided. Filters are designed for this specific use case and expected types. A filter designed for a particular user may be passed a class that is outside of the scope of the filter. For example, one of the applications of the filter is to black-list classes then it can reject a candidate class that matches and report undecided for others. Here it is in code:

  package com.techilashots.java9.features;   
   
  import java.io.ObjectInputFilter;   
   
  /**   
  * Demonstrates Java 9 Serialization/De-Serialization Filters for Incoming Data. Do Refer https://goo.gl/bRezWt for more   
  * on Filters, with more Details and Examples.   
  */   
  class ItemCatalogFilter implements ObjectInputFilter {   
   
    private long maxStreamBytes = 400; // Maximum allowed bytes in the stream.   
    private long maxDepth = 2; // Maximum depth of the graph allowed.   
    private long maxReferences = 5; // Maximum number of references in a graph.   
   
    @Override   
    public Status checkInput(FilterInfo filterInfo) {   
   
       if (filterInfo.references() < 0 || filterInfo.depth() < 0 || filterInfo.streamBytes() < 0   
            || filterInfo.references() > maxReferences || filterInfo.depth() > maxDepth   
            || filterInfo.streamBytes() > maxStreamBytes) {   
         // reject this, as it seems malicious, incorrect or tampered with   
         return Status.REJECTED;   
       }   
   
       Class<?> clazz = filterInfo.serialClass();   
       if (clazz != null) {   
         if (CatalogCustomer.class == filterInfo.serialClass()) {   
            // we are expecting a customer of our product catalog   
            System.out.println("Incoming Serialization Data Verified for Structure and Vulnerabilities");   
            return Status.ALLOWED;   
         } else {   
            // seems like some tampered, unexpected or malicious data here   
            return Status.REJECTED;   
         }   
       }   
   
       // the status as undecided, when we cannot infer - or + for sure   
       // left for the developer to decide as per business/security process   
       return Status.UNDECIDED;   
    }   
  }