In my opinion, Java is an amazing programming language which performs many behind-the-scene tasks automatically, letting the user focus only on the application logic.
Java Virtual Machine (JVM), the cornerstone of Java Platform, is responsible for handling most of the operations which are necessary for uninterrupted execution of a Java application. In this article, we will discuss five such interesting operations that JVM carries out surreptitiously. Though most of the Java developers are aware about the final results of these processes, many are unfamiliar with the underlying process or functional components that are involved.
Have you ever wondered how the JVM finds all the necessary classes required to run your Java application properly?
To run any Java application, Java Runtime Environment (JRE) has to load necessary classes from different resources in to Java Virtual Machine (JVM). For this, JRE has Class Loaders which does this dynamically based on several principles.
There are 3 built-in class loaders in Java.
1. Bootstrap Class Loader
Loads Java base classes within Java.* packages. Usually loads rt.jar from JRE library directory.
2. Extension Class Loader
Loads Java extension classes. Usually loads jar files from JRE ext directory.
3. Application (System) Class Loader
Loads classes from application classpath.
A class will be loaded dynamically into the JVM when it is referenced by a class that is already running in the JVM. Java uses Delegation Model for loading classes.
In the delegation model, when a Load Request arises, that request will be delegated step by step, from bottom to top-most loader. Then the searching will start from the top-most class loader and if class is not found on that level, the search will step down to the next loader and so on.
Java allows us to write our own custom Class Loaders, but in general, we don’t have to, unless we are working on a complex application that needs modularisation of system load or reload classes at runtime etc.
When a particular class is loaded into the JVM, all the class variables (static member variables) will be assigned default values of their type. This is because compiler assigns default values to all the member variables of a class in the compile time.
Additionally, a runtime process of class initialisation will be performed by the JVM for any class which is loaded into the memory by a Class Loader.
JVM will not run the class initialisation process immediately after the class loading. However, it will happen immediately before the first occurrence of any of the followings.
1. Creation of an object from that class
2. Calling a static method or using/changing a static variable
3. Execution of an assert statement which is nested within a top-level class
We can initialise static variable in a class by defining declaration values. Also, some of you may have also heard about Static Initialiser Blocks, which are used frequently for initialising static variables.
Java class initialization process involves following steps.
1. Assign declaration values to static variables
If a custom value declared for a static variable, it will be assigned to that respective variable.
2. Execute Static Initializer Blocks
All static initializer blocks defined in the class will be executed.
When we are creating an object from a class, all the instance variables will be initialised to their default values. This will happen at the same time when an object is created in the heap. Additionally, another process of instance initialisation will be executed by the JVM, just before returning the reference to the created object.
Unlike class initialisation which happens only once, the instance initialisation will happen each time when an object is created from a class (explicitly or implicitly).
We can initialise instance variables with our custom values by defining declaration values. Additionally, just like static initialiser blocks, there are Instance Initialiser Blocks which are also typically used for initialising instance variables in an object.
Java instance initialization process involves following steps.
1. Invoke super class constructor
Super class will be invoked explicitly or implicitly.
2. Assign declaration values to instance variables
If a custom value is declared for an instance variable, it will be assigned to that respective variable.
3. Execute Instance Initialiser Blocks
All instance initializer blocks defined in the class will be executed.
4. Execute constructor body
Super class already invoked. The rest of the code written in the constructor will be executed.
We are all aware that we don't have to worry about memory management in Java. Instead Java handles the Garbage Collection automatically letting us focus only on the application logic.
We are also able to manually trigger garbage collection by either calling System.gc() or Runtime.getRuntime().gc() methods, however the immediate execution is not guaranteed. Let's pay our attention to check how Garbage Collection works in Java.
Java Garbage Collector runs as a Daemon Thread (a low priority background thread) and it is responsible for the following tasks.
In Java, different Garbage Collector implementations use different algorithms and approaches for garbage collection. Each of these algorithms have their own advantages and limitations. Some of the algorithms divides heap memory in to two generations called young generation and old generation.
In Java 7 to Java 13, there were four garbage collector implementations in JVM. In Java 14, there are only three.
1. Concurrent Mark Sweep (CMS) Collector
Uses Mark Sweep algorithm in multiple threads. This collector has been deprecated and unavailable since Java 14.
2. Serial Collector
Uses Cheney’s copying algorithm and Mark Compact algorithm. Works on a single thread and freezes all other threads until garbage collection process is completed.
3. Parallel Collector
Uses the same algorithms as the Serial Collector, but runs on multiple concurrent threads. This was the default garbage collector in Java 7 & 8.
4. G1 Collector (Garbage-First Collector)
Segments heap in to multiple regions (typically 2048). Keeps track of live data in each region. Collects the garbage first from the regions which have the most garbage. This is the default collector since Java 9.
Similar to how the memory occupied by unused Objects is reclaimed by the Garbage Collection process, the classes which are loaded into the memory could be unloaded. Class unloading is an optimisation process performed by JVM to reduce the memory usage.
Even if there are no current references to a particular class, it will not be unloaded from memory if its defining Class Loader is still reachable. This is because even if there are no current references, it might be referenced by a class which is potentially loaded by the loader. Also, if a class somehow unloaded from memory while its defining Class Loader is potentially reachable, the class may be reloaded in future, if the loader loads another class which has reference to the said class.
This is because, the initial bootstrap class loader is permanently reachable throughout the JVM execution. Therefore any classes that it loaded will not be unloaded until programme exit.
We have discussed five major JVM internal processes that are essential to run a Java application. Knowledge about JVM internals will be useful particularly when working with a large scale enterprise application. Additionally, if you know how things are working internally, you are more confident in writing robust code and resolving any errors.
Thank You and Keep learning!