As I sit here and typing away on my Java-powered laptop, sipping coffee from my Java-branded mug, I can’t think: Is there anything in this world that doesn’t run on Java? It seems like everything from smartphones to smart refrigerators is powered by the JVM. At this rate, I wouldn’t be surprised if my toaster started brewing Coffee and sending emails using Java in the near future.
But let’s face it, Java has come a long way since its inception in the mid-90s. It evolved into a powerful and versatile language, and the JVM has become the backbone of the technology world. So, whether you love it or hate it, it’s hard to deny the impact that Java has had on the world. And who knows, maybe one day we’ll all be living in a Java-powered utopia, where even the birds tweet in byte code.
Let’s now examine the developmental progress of the JVM.
The purpose of this blog is to provide programmers with a comprehensive list of critical and essential information related to Java Virtual Machine. Whether you are a seasoned developer or just starting with Java, this guide covers the most important topics that every programmer should know. From memory management and garbage collection to class loaders and execution engines, this blog aims to provide a quick and easy reference to help you optimize your Java application’s performance and stability.
Bird Eye view of the JVM
Press enter or click to view image in full size
Lets take look at each component in detail,
Class Loaders
Class loaders play crucial role in loading classes and resources into memory. The class loader sybsystem follows a delegation hierarchy algorithm and consists of three types of class loaders, listed in a specific order.
Press enter or click to view image in full size
Bootstrap Class Loader
- The bootstrap class loader loads the core Java libraries located in the /jre/lib directory.
- These libraries are stored in Jar files called rt.jar, core.jar, server.jar etc
Extension Class Loader
- Extension class loader will search in extension class path (/jre/lib/ext) or any other directory specified by the java.ext.dirs system property
Application Class Loader
- Application Class loader will search in application classpath
Loading Process flow on a high-level
ClassNotFoundException Vs ClassDefNotFoundException
- ClassNotFoundException is thrown when the JVM tries to load a class at runtime, but it cannot find the definition of the class. This can happen when the class name is misspelled, the class file is missing or not in the classpath, or the class is not accessible due to permission issues.
- ClassDefNotFoundException is thrown when the JVM tries to load a class, but it cannot find the definition of the class even thorugh the class file is present. This can occur when the class file has an incorrect format, is corrupted, or the class definition is not compatiable with the current JVM.
Runtime Data Areas
Press enter or click to view image in full size
Method Area
- Contains class level data.
- PermGen until Java 7, its called Metaspace since Java 8
PC Register
- Holds address of current executing instructions.
Native method Stack
- Holds native method information.
Heap
In the JVM, the heap is the region of the memory used to store objects created by a Java program at runtime. When a Java program creates an object using the new operator, the JVM allocates memory on the heap to store the object.
Press enter or click to view image in full size
- By default Object level data will be saved (Object and corresponding instance variables, arrays etc.)
Eden
Eden is a region of the heap memory where new objects are created. When a new object is created, it is initially allocated in the Eden space. The Eden space is part of the young generation, which is one of the two main regions of the heap memory.
Survivor 1 & Survivor 2
The Survivor space is devided into two parts: Survivor 1 and Survivor 2, during each minor collection cycle, the JVM identifies objects in the Eden space that are still in use and move them to one of the Survivor spaces. The use of two Survivor spaces allows the JVM to reduce the frequency of garbage collection cycles and improve the performance of the Garbage Collection process. The generational copying technique also enables the JVM to optimize the memory usage and minimize the fragmentation of the memory space.
Old/Tenured Generation
The Old/tenured generation is a region of the heap where long-lived objects are stored. Its a space reserved for objects that have survived serveral rounds of garbage collection, indicating that they are less likely to be garbage and should be retained for a longer period.
Stack & Stack frames
A Stack is a region of memory used to store method invocations and local variables. Each thread in a Java program has its own stack, which is used to keep track of the method calls and local variables used by that thread.
Each method call in a Java creates a new stack frame on the thread’s stack. A stack frame, also known as an activation record or stack activation, is a data structure that contains information about the method being executed, the values of the method’s parameters and local variables, and the return address for the method. The stack frame is popped off the stack when the method completes.
Press enter or click to view image in full size
Permanent Generation
Also known as PERMGEN, it consists mostly of class declarations loaded and stored into PermGen. This includes below mentioned
- The name and fields of the class
- Methods with the method bytecode
- Consist pool information
- Object arrays and type arrays associated with a class.
- Just In Time compiler optimizations.
MetaSpace
From Java 8 and later versions, the PermGen space has been removed and replaced by the Metaspace. The Metaspace is a native memory region that is used to store class metadata instead of the heap.
Execution Engine
The Execution Engine is a crucial component of the JVM, responsible for executing bytecode instuctions that are generated by the Java compiler. It plays very important role in the proper functioning of Java programs, making it a critical component of the JVM.
Press enter or click to view image in full size
Few Glossary items to know in Execution Engine…
Interpreter
Interprets the Bytecode into platform specific machine code.
JIT Compiler
Just-In-Time compiler is a program that turns Java bytecode (a program that contains instructions that must be interpreted) into instructions that can be sent directly to the processor, just before it needs to be executed.
HotSpot Profiler
Looks for “hot spots” in the code, i.e. methods that the JVM spends a significant amount of time running, and then comiles those methods into native generated code.
Garbage Collector
Program which does Memory Management on the JVM by getting rid of objects which are not being used by a Java application anymore.
Few things we must know before we learn more about the GC
Mark & Sweep Algorithm
Mark & Sweep is a basic garbage collection algorithm used in JVMs to reclaim unused memory.
Generational Hypothesis
It is a principle in Garbage Collection that suggests that most objects are short-lived and become garbage relatively quick after they are created. This is the basis for the Garbage Collection process in many modern programming languages, including Java.
To keep it simple,
- Most of the objects soon become unreachable
- References from OLD objects to YOUNG objects only exist in small numbers.
Garbage Collection Roots are the object or references that are known to be reachable from outside the heap and are therefore considered live and not eligible for garbage collection. The garbage collector uses these roots as starting points to traverse the object graph and mark all reachable objects as live.
- Local Variables
- Active Threads
- Static Fields
- JNI References (Java Native Interface)
Marking is walking through all reachable objects, starting from GC roots and keeping a ledger in native memory about all such objects.
Sweeping is making sure the memory addresses occupied by non-reachable objects can be reused by the next allocations.
Minor Cycle is the first phase of the Garbage Collection process. It is responsible for freeing up memory used by short-lived objects that are no longer needed. The JVM identifies all the objects in the Eden space and the survivor space that are no longer in use. It then moves the surviving objects from the survivor space to the other survivor space.
Major Cycle is the second phase of the Garbage Collection process, unlike the minor cycle mentioned above, this is more responsible for freeing up memory used by long-lived objects in the old generation.
Press enter or click to view image in full size
Fragmented areas
Fragmented areas are regions of the heap that are left empty due to object allocation and deallocation. Over time, as objects are created and destroyed, the heap may be fragmented, with empty spaces scattered throughout the heap.
Compacting Phase
It’s a process of making the memory allocations contiguous by rearranging them.
Press enter or click to view image in full size
Press enter or click to view image in full size
Stop-The-World Pause is a type of pause that occurs when the JVM stops the execution of all application threads are paused and cannot make any progress until the Garbage Collection is complete.
Types of Garbage Collectors
Serial Collector
This existists from JSE 1.0, and it is a basic Garbage Collector. It is a single Threaded, Mark & Sweep Collector. Generally, it is used for small amount of Heap (approximately 100 MB). It runs in a single thread. It is best-suited to single processor machines. If there are no pause time requirements.
Press enter or click to view image in full size
When to choose?
- Applications with medium-sized to large-sized data sets that are run on multiprocessor or multithreaded hardware.
Parallel Collector
It is very similar to the Serial Collector, but multiple threads are used to speed up the Garbage Collection. It does not kick in until heap is full or almost full, and it does Stop-The-World pause when it runs. It’s biggest advantage is, it uses multiple threads to scan through and compact the heap.
Press enter or click to view image in full size
Downsides: It will stop all application threads when performing either a minor or full GC Collection.
When to choose?
- If peak application performance (Throughput) is the first priority
- If there are no pause time requirements or pauses of 1 second or longer are acceptable.
Concurrent Mark Sweep
CMS only collects Old Generation, and it doesn not wait for the Old Generation to be full. It’s known for its very low-pause time and many phases run concurrently with application.
Execution Phases:
Initial Mark — Pauses all application threads.
Concurrent Mark
Concurrent Preclean
Re-mark — Pause all application threads. (Longer than Initial mark)
Concurrent Sweep
Concurrent Reset
Press enter or click to view image in full size
When to choose?
- If response time is more important than the overall throughput
- If garbage collection pauses must be kept shorter than approximately 1 second.
Downsides:
- It causes heap fragmentation; as there is no compacting phase.
- Its pause times scale with the amount of live objects in its tenured region.
Garbage First (G1) Collector
G1 is planned as the long-term replacement for CMS. It is a server-style garbage collector, targeted for multi-processor machines with large memories. It supports heap size larger than 4GB, and its parallel, concurrent and incrementally compacting low-pause garbage collector. Its also been publicly stated by the Oracle GC Team that their hope for low pause GC is a fully realized G1, the default Garbage Collector is changed to G1GC from JSE 9.
Apps running today with either CMS or the ParallelOldGC garbage collector would benefit switching to G1GC if the application has one or more the following requirements.
The G1 collector divides the heap into multiple regions and collects the regions with the most garbage first, hence the name “Garbage First”. It uses a combination of parallel and concurrent phase to achieve high throughput and low pause times.
Press enter or click to view image in full size
- Large heap size
- Full GC durations are too long or too frequently.
- The rate of object allocation rate or promotion varies significantly.
- Undesired long garbage collection or compaction pauses (longer than 0.5 to 1 second)
Performance Tuning
Heap Size
The memory allocated for the old generation is the difference of -Xmx and -Xmn. Generally, you don’t want the Eden to be too big or it will take too long for the GC to look through it for the space that can be reclaimed. For efficient garbage collection, the -Xmn value should be lower than the -Xmx value. (For Production, it’s a good practice to keep both to the same value)
If Heap reaches the limit (-Xmx) and new allocations cannot happen, then JVM throws java.lang.OutOfMemoryError Heap Space
#Initial java heap size
-Xms#Maximum java heap size
-Xmx
#The Size of the heap for the young generation
-Xmn
Stack Size
#The Stack Size for each thread (example -Xss2048k )
-XssChoosing Garbage Collector(s)
- For Choosing Single Threaded garbage collector for both young and old generation
-XX:+UseSerialGC- For Choosing a multithreaded garbage collector for the Young Generation and a Single threaded for the Old Gen
-XX:+ParallelOldGC- For Choosing a multi threaded garbage collector for both Young & Old Gen, for achieving faster collections and shorter Stop-The-World times
-XX:+UseParNewGC- For Choosing a multi threaded garbage collector for the young generation. (Compatible with -XX:+UseConcMarkSweepGC)
-XX:+UseConcMarkSweepGC- In Order to the GC sweep PermGen and remove classes which are no longer used. (You have to use UseConcMarkSweepGC also to make this work)
-XX:+CMSClassUnloadingEnabled- To make use of the G1
-XX:+UseG1GCGarbage Collector Logs
Setting these flags writes all the garbage collections made by the JVM to a log file (or stdout). Using this information you can fine tune your garbage collection configuration.
#To print right after each garbage collection
and print details about each generation memory details.
-verbose:gc #To Print the Garbage Collection Details
-XX:+PrintGCDetails
#To Include TimeStamp Information
-XX:+PrintGCTimeStamps
#Writes GC logs to an external log file at ~/.{temp}/gc.log
-Xloggc:~/.{temp}/gc.log
Tenuring Threshold
During young space collection, every single object is copied. The Object may be copied to one of survival spaces (one which is empty before young GC) or to the old space. For each Object being copied, GC algorithm increases its age (number of collection survived) and its if the age is above the current tenuring threshold it would be copied (prompted) to old space.
-XX:MaxTenuringThreshold=threshold- Sets the maximum tenuring threshold to use in adaptive GC sizing
- The Largest value is 15
- the default value is 15 for the parallel (throughput) collector.
- The default value is 6 for the CMS Collector.
The following example shows how to set the maximum tenuring threshold to 10.
-XX:MaxTenuringThreshold=10Summary
Long Live Java !!!
The evolution of Java and the JVM has been an exciting journey. From its initial release in 1995 to the latest version of JDK 20, Java has come a long way, introducing new features and improving existing ones with each new release. The language has become one of the most widely used programming languages in the world, and there is no doubt that Java will continue to play a crucial role in the development of Software for years to come.
If you found my blog posts enjoyable and gained new insights, I kindly ask you to think about sharing them and joining me here for future updates. Please reach out to me on LinkedIn for any questions or suggestions.