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

Java 9 to 19

Java 8 is still extensively used in the industry and many applications will gradually shift to newer Java version, especially the LTS versions.

In this post we will take a look at the evolution happened in Java language from Java 9 to Java 19. Note that each version comes with many improvements, bug fixes and variety of features, we will cover the ones which are majorly used and can impact our day to day developement.

Java 9

Factory methods for collection

    List immutableL = List.of(1, 2, 3);
    Map immutableM = Map.of(1, "ONE", 2, "TWO", 3, "THREE")

JShell: Java Shell, or REPL (Read Evaluate Print Loop) to execute java constructs directly in command line.

Private methods in interface.

This will avoid code duplication and better separation of concern when it comes to implementing default and static methods in interface.

interface Student {
    private String joinNames(String firstName, String lastName) {
        return String.join(firstName, " ",lastName);
    }
    private static String schoolName() {
        return "Some School";
    }

    default String id(String firstName, String lastName) {
        String fullName = joinNames(firstName, lastName);
        return schoolName() + "\n" + fullName;
    }
}

Step in direction to optimize String concatenation.

For the given class,

public class Test {
    public static void main(String[] args) {
        String str = args[0] + " and " + args[1];
    }
}

If we compile and check the bytecode, we can notice significant different in the way concatenation is handled.

In Java 8,

➜  java git:(main) ✗ java -version 
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (build 1.8.0_362-bre_2023_01_22_03_30-b00)
OpenJDK 64-Bit Server VM (build 25.362-b00, mixed mode)
➜  java git:(main) ✗ clear           
➜  java git:(main) ✗ java -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (build 1.8.0_362-bre_2023_01_22_03_30-b00)
OpenJDK 64-Bit Server VM (build 25.362-b00, mixed mode)
➜  java git:(main) ✗ javac Test.java
➜  java git:(main) ✗ javap -c  Test 
Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: aload_0
       8: iconst_0
       9: aaload
      10: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      13: ldc           #5                  // String  and
      15: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      18: aload_0
      19: iconst_1
      20: aaload
      21: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: astore_1
      28: return
}

In Java 9,

➜  java git:(main) ✗ java -version 
openjdk version "9"
OpenJDK Runtime Environment (build 9+181)
OpenJDK 64-Bit Server VM (build 9+181, mixed mode)
➜  java git:(main) ✗ javac Test.java
➜  java git:(main) ✗ javap -c  Test 
Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aload_0
       1: iconst_0
       2: aaload
       3: aload_0
       4: iconst_1
       5: aaload
       6: invokedynamic #2,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      11: astore_1
      12: return
}

Notice the multiple StringBuilder invocations in case of Java 8, which is replaced with makeConcatWithConstants in Java 9.

Java 10

Local variable type interface, use var to declare.

    var i = 1;
    var str = "Hello";
    var student = getStudent();

Static factory methods to create immutable copy of Collection,

    List<String> immutable = List.copyOf(otherList);

orElseThrow() in Optional

    Optional<Object> optional = Optional.ofNullable(null);
    optional.orElseThrow(() -> new RuntimeException("Something went wrong!"));

Java 11 (LTS)

Execute Java file directly without compiling with javac.

java command internally takes care of the compilation.

Some helper methods for String

String str = "Hello";
boolean isBlank = str.isBlank();
str.lines().forEach(System.out::println);
str = str.strip();
// str.stripLeading();
//  str.stripTrailing();

Removed deprecated packages,

java.xml.ws
java.xml.bind
java.activation
java.xml.ws.annotation
java.corba
java.transaction
java.se.ee
jdk.xml.ws
jdk.xml.bind

Make file read and write convenient,

Path path = Files.writeString(Files.createTempFile("temporary", ".txt"), "Something to write!");

String fileContent = Files.readString(path);
System.out.println(fileContent);

Java 12

Switch can be an expression, changes are in preview.

Before Java 12

Animals animal = Animals.COW;

switch (animal) {
    case COW:
    case GOAT:
        System.out.println("Herbivore");
        break;
    case TIGER:
    case LION:
        System.out.println("Carnivore");
        break;
}

Can now be reduced to,

String animalType = switch (animal) {
    case COW, GOAT -> "Herbivore";
    case TIGER,  LION -> "Carnivore";
};

No need to typecast for instanceof,

if(object instanceof String) {
    System.out.println(((String)object).toUpperCase());
}

but now you can do,

if(object instanceof String str) {
    System.out.println(str.toUpperCase());
}

Compare files,

try {
    Path filePath1 = Files.createTempFile("abc1", ".txt");
    Path filePath2 = Files.createTempFile("abc2", ".txt");

    Files.writeString(filePath1, "Hello!");
    Files.writeString(filePath2, "Hello! (Diff)");

    long mismatchIndex = Files.mismatch(filePath1, filePath2);
    if(mismatchIndex == -1) {
        System.out.println("Both files are same!");
    } else {
        System.out.println("Mismatch found at " + mismatchIndex);
    }
} catch (IOException e) {
    throw new RuntimeException(e);
}

Output

Mismatch found at 6

String Identation,

String str = "Hello";
for(int i = 0 ; i < 5; i++) {
    System.out.print(str.indent(i));
}

Output

Hello
 Hello
  Hello
   Hello
    Hello

Convenient method to transform String,

String numbers = "1:ONE,2:TWO,3:THREE";
Map<Integer, String> map = numbers.transform(input -> {
    Map<Integer, String> output =
        Arrays.stream(input.split(","))
            .collect(Collectors.toMap(i -> Integer.parseInt(i.split(":")[0]),
                i -> i.split(":")[1]));
    return output;
});
System.out.println(map);

Java 13

Text block support in String,

String textBlock = """
    I can write anything,
    without adding \\n in the String.
    """;
System.out.println(textBlock);

New Methods in String for format,

String anything = "Hello %d and %s".formatted(1, "ONE");

Intoduced yield in switch case, this will replace the break for cases where we want to return the number. Difference between yeild and return is that yeild will return the value to switch invocation while return will return the value to the caller of the method.

int answer = switch (number) {
    case 1:
    case 3:
    case 5:
    case 7:
        yield number;
    default:
        yield -1;
};

Java 14

Preview of records, a data class.

record Person(String name, int age){}

Can be used,

Person person = new Person("Human", 999);
System.out.printf("Person %s, age %d\n", person.name(), person.age());

Things to note about record

  • Can not extend, can not be extended by class
  • Can not be abstract
  • Allows static fields and methods
  • Instance fields can be declared during initialization.
  • Declared fields are private and final
record Person(String name, int age){
    // int anything = 0; // Not allowed
    static int anything = 0;

    public String personDetails() {
        return String.format("Person %s, age %d\n", name(), age());
    }
    Person {
        if(name == "Human") {
            throw new RuntimeException("Invalid name");
        }
    }
}

Records can implement interfaces,

interface Human {
    public String personDetails();
}

record Person(String name, int age) implements Human{
    public String personDetails() {
        return String.format("Person %s, age %d\n", name(), age());
    }
}

It can support multiple constructors as well,

record Person(String name, int age){
    public Person() {
        this("Human", 9999);
    }
    public Person (int age) {
        this("Human", age);
    }
}

Allow trailing space in text block,

String textBlock = """
    I can write anything,
    without adding \\n in the String.\s\s\s
    """;
System.out.println(textBlock);

Java 15

Preview of sealed classes or interfacse,

to allow only specific types which can extend or implement respectively.

public abstract sealed class Animal permits Herbivore, Carnivore {
}

final class Herbivore extends Animal{}
sealed class Carnivore extends Animal{}

//class Unknown extends Animal{} // Not allowed to extend

Subclass of a sealed class must have either of the following modifiers,

  • sealed : Will allow to be extended further by permitted classes.
  • non-sealed : Will allow to be extended further by any classes.
  • final : Will not allow to be extended further.
public abstract sealed class Animal permits Herbivore, Carnivore, Omnivore {
}

final class Herbivore extends Animal {}
sealed class Carnivore extends Animal permits  Tiger{}
non-sealed class Omnivore extends Animal {}

final class Tiger extends Carnivore{}

Records can implement the sealed interfaces,

sealed interface Food permits Creature {
    void doSomething();
} 
record Creature(String name) implements Food {
    @Override
    public void doSomething() {
        System.out.println("Anything");
    }
}

Java 16

Pattern matching in instanceof no longer makes variable implicitly final

if(object instanceof String) {
    object = String.format("Result %s", object); // Would give compile time error prior to Java 16.
    System.out.println(object.toUpperCase());
}

New Vector API, incubator.

int[] odd = {1, 3, 5, 7};
int[] even = {2, 4, 6, 8};
var vector1 = IntVector.fromArray(IntVector.SPECIES_128, odd, 0);
var vector2 = IntVector.fromArray(IntVector.SPECIES_128, even, 0);
var vector3 = vector1.add(vector2);
System.out.println(vector3);

Output

[3, 7, 11, 15]

Note that, to run the program you will need to add the module otherwise it won’t be visible.

 java --add-modules jdk.incubator.vector JavaMainClass

Java 17 (LTS)

null in switch,

switch (number) {
    case 1, 2, 3 -> System.out.println("Valid");
    case null -> System.out.println("Not available");
    default -> System.out.println("Invalid");
}

Pattern matching in switch,

String value = switch (obj) {
    case Integer i -> "Integer";
    case Long l    -> "Long";
    case Double d  -> "Double";
    case String s  -> "String";
    case null -> "NULL";
    default -> obj.toString();
};

Java 18

Introduce @snippet in JavaDoc to write code in comments,

/**
* {@snippet:
*  int a = 10;
* }
*/
public void testMethod() {

}

Finalization is deprecated.

The use of finalize() method is discouraged and the support will be removed in future.

Java 19

Preview Virtual Threads

lightweight threads which effectively shares the platform thread for optimal hardware utilisation.

ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();

Structured Concurrency (Incubator)

Sounds interesting, it enables to consider multiple threads as a unit. This will going to give better control over multithreaded programs.

Basics of Spring Transactional

Why Spring Transaction?

  • Provides consistent model across JTA (Java Transaction API), JPA (Java Persistence API), JDBC (Java Database Connectivity) and JDO (Java Data Objects)
  • Declarative Transaction Management
  • Simple API for programmatic transaction management - overcomes complexity of JTA.
  • Makes the implementation easy to stub and test.

Transaction with JDBC

import java.sql.Connection;

//...
public void deleteSomething() {
    Connection connection = DriverManager.getConnection(host, user, password); 

    try (connection) {
        connection.setAutoCommit(false);
        Statement deleteStatement = connection.createStatement(); 
        String deleteQuery = "DELETE FROM SOMETHING WHERE SOMETHING_ID = 1";
        deleteStatement.executeQuery(deleteQuery); 
        connection.commit();
    } catch (SQLException e) {
        connection.rollback();
    }
}

//...
With raw JDBC we need to handle commit, rollback, savepoint all on our own. Spring takes away majority of boilerplate code so that we can focus on core business functionalities. 

Transaction with Spring

Above function can be implemented in following way with Spring (assuming the transaction management configuration is in place),

@Transactional
public void deleteSomething() {
    somethingRepository.deleteBySomethingId(1);
}

Spring Transaction Manager




Spring transaction manager mainly rely on definition and status. In definition the nature and behaviour of transaction is defined while the status helps to keep track of transaction journey statuses.

Different Supported transaction managers includes,
  •  DataSourceTransactionManager
  •  JtaTransactionManager
  •  HibernateTransactionManager
  •  JdbcTransactionManager
  •  etc.
This Transactional annotation handles everything behind the scene for use. This annotation has two important configurations - Isolation and Propagation, which we will understand in detail.

Propagation

Transcation propagation defines the nature of transaction handling when there are multiple transactional annottated methods are invoked within the same spring transcation context. 

You can suppy the propagation in Transactional annotation,

@Transactional(propagation = Propagation.REQUIRES_NEW)

Following are supported propagation ways,

  • REQUIRED  → Create new transcation or use existing.
  • SUPPORTS  → Work with or without transaction.
  • MANDATORY  → Do not create transaction but fail if transaction do not exist.
  • REQUIRES_NEW  → Create a new transaction.
  • NOT_SUPPORTED  → Execute without transaction, suspend if transaction exists.
  • NEVER  → Execute without transaction, fail if transaction exists.
  • NESTED  → One physical transaction with multiple savepoints to manage subtransactions.
















Isolation

Isolation level configuration determines the visibility of data between transactions in case of concurrent operations on same database resource. It helps to ensure consistency and integrity of the data. Transaction isolation level is subject to underlying database you are using. We will refer to them in generic sense to get glimps of it.

You can suppy the isolation in Transactional annotation,

@Transactional(isolation = Isolation.DEFAULT)

The isolation levels tackles different concurrent transcation phenomena listed below,
  • Dirty Read : Transaction read uncommitted data of other transaction.
  • Non-repeatable read : Re-read the same data which is now modified by other transaction.
  • Phantom Read : Re-execute the query which returns result set which changed by other transcation.
  • Serialization Anomaly : Inconsistent state of data - group of transactions are committed sequentially with all possible ordering.

Following are supported isolations levels in Spring,
  • DEFAULT → User default isolation level of underlying database.
  • READ_UNCOMMITTED → Allows to read uncommitted data of other transaction. It can not prevent problems may arrise due to concurrency.
  • READ_COMMITTED →  Allows to read committed data of other transaction. Prevents dirty read.
  • REPEATABLE_READ → Prevents dirty read and non-repeatable read (and even phantom read) by returning unchanged data in repeatable reads within same transaction.
  • SERIALIZABLE → Executes transactions sequentially. Solves all consurrency problems but performance suffers.

This can be a starting point for spring transaction learnings, if you have understood the propagation and isolation level well, it becomes easier to configure and debug accordingly. 

Virtual Threads : Java 21

Java 21 came with an exciting feature of Virtual Thread - Lightweight Threads. The idea is to achieve optimal hardware utilisation, which has been a bottleneck for the conventional java.lang.Thread - platform threads.

Platform threads are 1:1 mapped with OS Thread, which makes them powerful to accomplish all possible tasks. java.lang.Thread is a thin layer on actual OS threads, which are quite heavy. The number of thread application can support depends on the hardware capacity of the system.

Let’s say thread memory consumption is 1 MB, to support 1 million threads in concurrent application at the time of heavy load, you will need 1 TB of memory. One thread per request style of implementation also suffer due to this limitation - asynchronous programming can solve this upto some extent but it has its own drawbacks.

Virtual threads effectively shares the platform thread. Rather than holding the platform thread for entire lifetime, it runs short lived tasks and for the needed executions - not while it waits for I/O. This allows insane number of concurrent operations without need of additional threads or hardware capacity. This brings up the new way to deal with concurrency in Java applications.

Virtual threads can be created with java.lang.Thread builder approach,

Runnable function = () -> System.out.println("Something to execute in Virtual Thread!");
Thread virtual = Thread.startVirtualThread(function);

In addition to that, java.util.concurrent.ExecutorService also has factory method for Virtual threads,

ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();

Virtual threads are daemon threads, join() to wait on the main thread. Thread local variables of virtual thread are not accessible to carrier thread or vice versa.

Deep vs Shallow Copy

In programming language like Java, everything revolves around objects. Lifecycle of object from creation to garbage collection is something that keeps happening continuously in any realtime application.

There are different ways you can use to create and Object of a Class. However, the object creation with new keyword is the straightforward one.

Car mercedes = new Mercedes();

While we keep creating objects on-demand, in some realtime scenarios we may need to create new object which is a copy of existing object. In that case new Object should hold same state as current Object is holding.

You may ask, what kind of scenarios require copying existing object?

Well, it completely depends on the software you are developing but in any application where you see copy option, whether it can be copying table row, copying form etc. such cases are good candidates to use object copying mechanism.

There are two approaches you can use to copy object,

  • Shallow copy
  • Deep copy

There are different methods to implement copying approach,

  • Using clone method
  • Copy constructor
  • Static factory method

There are certain third party libraries available which provides methods to copy the objects.

For example, BeanUtils#copyProperties(Object destination, Object source)

Which option to choose majorly depends on your requirement.

Shallow copy

All the fields of existing object is copied to new object.

Consider following diagram, while copying Car object, company object reference is reused in the copy object, which simply means shallow copy only copies values (variable and object references).

Basically, it doesn’t create copy of objects referenced inside the object we want to copy, thus it is called shallow copy.




















Following is a shallow copy example using copy constructor method,

class Car {

    private String model;

    private Company company;

    public Car(String model, Company company) {
        this.model = model;
        this.company = company;
    }

    public Car(Car carToCopyFrom) {
        this(carToCopyFrom.model, carToCopyFrom.company);
    }

}

Deep copy

All the fields along with the objects referenced by existing object are copied to new object.

Consider following diagram, company object is also copied and reference to this new object is used in car object. Note that all the referenced objects at any level (direct or indirect) are copied and referred in copy object.




















Following is a deep copy example using copy constructor method,

class Car {

    private String model;

    private Company company;

    public Car(String model, Company company) {
        this.model = model;
        this.company = company;
    }

    public Car(Car carToCopyFrom) {
        this(carToCopyFrom.model, new Company(carToCopyFrom.company.getName()));
    }
}

Java EE to Jakarta EE


Jakarta EE is nothing but aquisition and rebranding of Java EE done by Eclipse Foundation.  By the time I'm writing this post, Jakarta EE 10 is the latest released version.

Few years back Oracle decided not to manage Java EE anymore and give it's ownership to an open source foundation.  Finally, it was given to Eclipse Foundation, however, the Java trademarks including existing specification names can not be used by Jakarta EE specifications. Thus javax.* could not be used as package due to which it had to be renamed to something else, which decided to be jakarta.*. 

So it was shift of ownership from Oracle to Eclipse Foundation. This shift ended up with concerns of backward compatibility for existing libraries and frameworks which were heavily relying on Java EE. 

Basically, all the users of Java EE had to switch to this Jakarta EE in near future for long term support and updates with the cost of backward compatibility. Even Jakarta EE didn't explicitly addressed this issue but consumers of Java EE realized and started moving to Jakarta EE gradually seeing the first release of Jakarta EE 8.

This has impacted different frameworks, application servers and libraries al together including Spring, Glashfish, Tomcat, Jetty, JBoss, etc. 

Few months ago Spring Boot 3 got released with GA having new baseline of Java 17 and Jakarta 9 (and support for Jakarta EE 10 as well). As you see not just Jakarta EE but even Java 17 is being pushed to be considered for upgrade considering it to be LTS. In case you are using older pre-jakarta Spring version and looking forward to upgrade to Spring Boot 3, you have to deal with updating your classes and interfaces using javax.* to jakarta.* imports if you have any.

IntelliJ Idea has option of doing this refactoring of imports Refactor > Migrate Packages and Classes > Java EE to Jakarta EE.











Bottom line is, everything which was under javax.* can now be found and referred at jakarta.*. In upcoming days we may see major changes at Jakarta which will make migration even difficult if we don't switch to it in near future. 


References :

Java 17 PRNG API

Java 17 come up with new API for Pseudo-Random Number Generators. There are several resons behind introducing new PRNG API. Which provides uniform ways to use existing and new PRNG algorithms.

New interface is introduced due to following reasons,

  • ThreadLocalRandom extends Random class and overrides pretty similar set of methods which can be avoided by using interface.
  • To replace usage of SplittableRandom PRGN objects with Random we have to change every variable even though the classes having some similar behaviors.
  • Code duplication in Random, SplittableRandom and ThreadLocalRandom
  • Make it easy to introduce new PRGN implementation without much code changes.
  • Provide support for PRGNs which are jumpable or leapable. (i.e. Xoroshiro128+)

What does new Random generator API provides?

Following diagram can explain hierarchy of random generators,

As we can see RandomGenerator is the parent interface extended by StreamableGenerator interface which enables streams of object to generate statistically independent objects.

Two child interfaces SplittableGenerator and JumpableGenerator extends StreamableGenerator interface which are introduced as part of Java 17.

Also, old Random class is now extending the RandomGenerator, which is done to bring older random generation into one umbrella and preserve it. As a result child class SecureRandom now automatically supports RandomGenerator. Thus we won’t need to reimplement SecureReandom class or it’s associated implementations. 

SplittableGenerator

Allows to create new SplittableGenerator from existing one which will generate statistically independent results. After split, high probability is that both objects will together generate values with same statistical properties like it is generated by single object. Provides two major split() and splits() methods to return split off and effectively unlimited stream of splits from existing generator.

Worth to note that the SplittableRandom (which is an implementation of SplittableGenerator) instances are not thread-safe.

Java 17 also has implementation of LXM family of PRNG algorithms which includes following classes,

  • L32X64MixRandom
  • L32X64StarStarRandom
  • L64X128MixRandom
  • L64X128StarStarRandom
  • L64X256MixRandom
  • L64X1024MixRandom
  • L128X128MixRandom
  • L128X256MixRandom
  • L128X1024MixRandom

JumpableGenerator

Allows to generate random values and jump to a point in state cycle.

Provides jump() method, which jump forward to fixed distance (typically 264). Also, jumps() method returns effectively unlimited stream of object post jumping forward. This are major methods, but it also provides other methods as well like jumpDistance() which returns the distance by which the jump() method will jump forward, copyAndJump() which copies existing generator, jumps it forward and returns the copy etc. Unlike split() which returns the split off object, jump() is void which just jumps.

LeapableGenerator interface extends JumpableGenerator. Which allow to jump ahead a large number of draws.

Following implementations of JumpableGenerator are provided in Java 17,

  • Xoshiro256PlusPlus
  • Xoroshiro128PlusPlus

Sample program to get implemented random generators in Java 17, Output:
Provided RandomGenerators :
L32X64MixRandom
L128X128MixRandom
L64X128MixRandom
SecureRandom
L128X1024MixRandom
L64X128StarStarRandom
Xoshiro256PlusPlus
L64X256MixRandom
Random
Xoroshiro128PlusPlus
L128X256MixRandom
SplittableRandom
L64X1024MixRandom
---------
Create RandomGenerator :Xoshiro256PlusPlus
-1083384208

Reference :

JVM Overview

 

What is JVM?

JVM stands for Java Virtula Machine. To understand JVM let’s first understand what exactly VM (Virtual Machine) is.

Virtual Machine, as name suggests, it is a machine which is virtual (non-physical). Basically, VM is a software which behaves like an actual physical machine. You can run one or more VM on actual physical machine, just like we use any applications on our laptop.

Alright, so now we have basic idea of what VM is, let’s understand JVM.

JVM is an abstract computing machine which…

  • extends runtime environment for execution of Java bytecode.
  • enables operating system independence of Java.
  • enables hardware inpenedence of Java.
  • protects users from malicious programs. etc.

JVM has different responsibilities and features but in nutshell it executes provided Java byte code instructions.Actually, it does not even know anything about Java, it just knows about class file format which contains the instructions (or bytecode).

JVM Architecture


Class Loader Subsystems

To execute the byte code in JVM it first needs to be prepared for execution, this class loader subsystem takes care of that with the help of following steps,

  • Loading : Loads binary representation of class or interface into method area.
  • Linking : Includes verification, preparation and resolution
  • Initializing : Executes initialization method <clinit>, of class or interface.

Let’s understand each of them in more details.

Loading

Loading reads .class file and generates implementation specific bytecode and stores it into method area.

Threre are three categories of class loaders in JVM,

  • Bootstrap Class Loader
    • Loads rt.jar file (contains essential java runtime libraries (classes)).
    • Starting point of class loading process
    • Written in Native code
  • Extension Class Loader : Loads Jar located inside $JAVA_HOME/jre/lib/ext directory. (extension to existing runtime libraries)
  • Application Class Loader : Loads files from the classpath.

With following program we will be able to understand a bit more,


Output
Class Loaders used for CheckClassLoader class
sun.misc.Launcher.AppClassLoader
sun.misc.Launcher.ExtClassLoader
--
Class Loaders used for String
null 

As you can see for our CheckClassLoader, AppClassLoader is used. Also, the parent of AppClassLoader is ExtClassLoader which is nothing but Extension Class Loader.

Apparently, the String class is directly loaded with BootStrap Class Loader which is written in native code and thus we don’t get Java class of it and the program prints null.

The overall process can be summarized with following diagram.

 

Linking

Linking step involves following,

  • Verification of binrary representation structure. If the verification of binanry represnetation doesn’t match with the expected static and structural constraints, VerifyError is thrown.
    • Constraints includes,
      • Static constraints ensures correct form of the binary representation.
      • Structural constraints ensures well defined relationship between the instructions.
  • Preparation creates static fields and initializes them with default values. This step does not require execution of JVM code but explicit initializers are executed.
  • Resolution of symbolic references of run-time constant pool.
    • JVM instruction like getstatic, invokespecial, invokevirtual, instanceof, new etc. rely on symbolic references in run-time constant pool.
    • Runtime constant pool is used for multiple purposes but in this context specifically, it contains a symbol table. Basically, it provides the actual entity associated with the given symbolic reference.
Consider following HelloWorld program, if we disassemble our HelloWorld with javap we can see multiple code items listed, which includes symbolic references (i.e. ldc, getstatic etc.). So, the resolution step actually resolves these symbolic references with the help of runtime constant pool.

Initialization

This step is all about executing initialization method of class or interface.

However, specific class or interface can only be initialized in one of the following cases :

  • Due to execution of JVM instructions new, getstatic, putstatic or invokestatic which references the particular class.
  • Due to initialization of its subclasses.
  • If it is designated as the initial class or inteface at JVM start up.
  • If its an interface and the initialization of a class that implements it directly and indirectly happens.
  • Through invocation of reflective method.

Runtime Data Area

JVM holds multiple data (memory) areas which are utilised for different purposes. Two kinds of data areas exists,

  • JVM Level : Created on JVM start up and gets cleared up on JVM exit.
  • Thread Level : Created for individual Java Thread and gets destroyed when Thread exits.

Following are different data areas that exists in JVM,

pc Register

pc stands for Program Counter, used to store context of the thread or in other way gives address of JVM instruction being executed for particular thread. This context is usefull to support multi threaded environment by JVM.

pc Registers are simply data which provides information about Thread’s state to the JVM.

JVM Stacks

JVM Stacks is created for each thread and it stores frames.

Each Thread will have some methods to execute, for such execution we need to store following data somewhere,

  • sequence of methods
  • local variables of particular method
  • partial results during method execution

JVM Stacks can be fixed size or dynamically expanding. Also, the memory allocation for JVM stack need not to be contagious.

We can use -Xss argument to allocate size to JVM stack.

java -Xss 32m HelloWorld

Two well known exceptions can occur due to JVM Stacks,

  • StackOverflowError if computation required by Thread requires higher stacks than permitted by JVM stacks.
  • OutOfMemoryError if JVM stacks is dynamically expanded but expansion is not possible due to insufficient memory.

Heap

Heap is created on JVM start up and shared across all JVM threads. This is the place where memory for objects and arrays are allocated.

Heap for objects is reclaimed by Garbage Collector (automatic memory management system of Java). Heap can be of fixed size or can expand or contracted based on requirement.

Memory allocated to heap need not to be contagious in nature.

We can configure the heap size with following JVM arguments,

  • -Xms : size of heap required on start up.
  • -Xmx : maximum allowed size of heap.

In case the memory is not available to allocate to the Heap, it throws OutOfMemoryError.

Method Area

Method area is shared among the JVM threads and created during the JVM start up.

It stores, run-time contant pool, field and method data, code for method data etc. per class basis.

Logically method area is part of the heap. Similar to heap, the memory allocated to method area need not to be contagious. Also, it can be fixed size or expandable (and contractable as well).

In case the memory is not available to allocate to Method Area it throws OutOfMemoryError.

The overall runtime data area can be visualised with following diagram,

 

Execution Engine

This is core of JVM which executes the Java byte code. It actually converts the bytecode to machine code and executes it.

It has three major components,

  • Interpreter
    • Reads and converts byte code to machine code.
    • Interprets line of instruction to machine instruction in isolation.
    • One drawback is, it actually interprets everytime, even the same method comes multiple times which decreases the performance of the system.
  • JIT (Just In Time) Compiler
    • The compilation from byte code to machine code is done at runtime in optimized way.
    • Increases performance by overcoming slow execution drawback of interpreter.
  • Garbage Collector
    • Performs automatic memory management for Java.
Note : For the sake of simplicity, things like native methods, class format structure, workings of garbage collector etc. are kept out of the scope of this article.

Reference :