JVM Architecture – Explained in Detail

Updated on October 23, 2024

Article Outline

A Java Virtual Machine (JVM) allows Java to be a cross-platform language. It acts as a virtual engine that decodes and runs Java programs in ByteCode format irrespective of the Operating system and particular features of the target platform.

 

For developers, the knowledge of the JVM architecture allows writing more efficient code, improving its performance, and resolving issues. However, this is not just any process of executing the program but rather entails critical tasks of memory management and garbage collection.

 

In this blog, we will discuss the most important elements of the JVM like memory areas, class loaders, the execution engine, garbage collection, and performance optimisation techniques.

 

What is the JVM?

JVM stands for Java Virtual Machine and is defined as a software power engine for executing Java programs. Its key function is to transform Java’s bytecode into low-level commands that are unique to a given system’s hardware, assuring that the Java-based applications can run on different systems.

 

Based on the idea of a ‘write once, run everywhere’ concept of Java, the JVM guarantees that the Java code can be executed on any computer system with a compatible JVM installed. Hence, programmers do not have to perform modification or recompilation of codes for the various operating systems.

 

As an intermediary between compiled bytecode and machine code, the JVM is responsible for memory management, garbage collection and security checks. This intermediary role makes sure that the program executes in the most efficient manner without compromising on a stable runtime environment.

*Image
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure

Components of JVM Architecture

Here, we have explained 5 components of the JVM architecture.

ClassLoader Subsystem

The JVM loads classes dynamically, meaning classes are only brought into memory when needed during program execution. This dynamic class loading allows Java programs to run efficiently by loading resources on demand rather than all at once. The process of class loading is handled by the ClassLoader subsystem, which ensures that classes are available when required.

 

The Process of Class Loading

 

The class loading process consists of three main steps: Loading, Linking, and Initialisation.

  1. Loading:
  • As the name suggests, during the loading phase, the ClassLoader loads the class which was needed into memory in the form of a ‘.class’ file. The class file is read, its format is checked and a binary form of the class is constructed.
  • Next, the class is loaded into memory, the ClassLoader begins with the Bootstrap ClassLoader, preferably the Extension ClassLoader and proceeds with the System/Application ClassLoader.

     2. Linking:

 

   2.1    Linking consists of three sub-steps: Verification, Preparation, and Resolution.

  • Verification: Ensures the loaded class is correctly formatted and follows the Java language specifications.
  • Preparation: Allocates memory for static variables and sets default values for them.
  • Resolution: Converts symbolic references in the class to direct references. This step may be deferred until the class is used.

       3. Initialisation:

 

  • During initialisation, static variables are assigned their initial values, and static blocks in the class are executed. This step happens just before the class is used for the first time.

 

Also Read: Difference Between JDK, JRE and JVM

 

Code Example: Class Loading in Java

The following example demonstrates how the JVM loads a class dynamically using the Class.forName() method.

public class ClassLoadingExample { public static void main(String[] args) { try { // Dynamically load the class Class<?> clazz = Class.forName("java.util.ArrayList"); System.out.println("Class loaded successfully: " + clazz.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }

Explanation:

  • In this example, Class.forName(“java.util.ArrayList”) dynamically loads the ArrayList class into the JVM.
  • If the class is successfully loaded, the program prints the class name; otherwise, an exception is thrown if the class is not found.

How Classes are Loaded on Demand

Classes in Java are loaded on demand which means that there is no need to load a class until it is used for the first time. Let’s say that a method within a class is not called, the class may not get loaded at all. This method helps improve memory usage and performance of the program as the loading of classes that are not needed does not take place.

 

The hierarchical delegation model used in class loading ensures that classes are loaded in a specific order:

 

  • The Bootstrap ClassLoader first attempts to load core Java classes from the standard libraries.
  • If the class is not found, the Extension ClassLoader searches in the extension directories.
  • If the class still cannot be located, the System/Application ClassLoader loads classes from the specified classpath.

 

By following this process, the JVM ensures that classes are available for execution as needed, without overloading memory with unnecessary resources.

Runtime Data Area (Memory Area)

The JVM organises memory into various runtime data areas, each with a specific purpose. These memory areas store data and execution information necessary for running Java programs.

 

  1. Method Area:
  • The method area stores metadata about classes, including class structures, methods, constants, and static variables.
  • It is shared among all threads, and memory management for this area is handled by the garbage collector.

 

     2. Heap:

  • The heap is the memory area where Java objects and instance variables are allocated.
  • It is also shared among all threads, making it suitable for objects that need to be accessed globally.
  • The garbage collector automatically reclaims memory from unused objects in the heap.

 

     3. Stack Area:

  • The stack area is used for method execution, storing local variables, partial results, and method call frames.
  • Each thread has its own stack, and data stored here is thread-specific.
  • When a method is called, a new stack frame is created; when the method completes, the stack frame is removed.

     4. Program Counter (PC) Register:

  • The PC register keeps track of the current instruction being executed in the thread.
  • Each thread has its own PC register, allowing the JVM to manage multiple threads concurrently.

 

     5. Native Method Stack:

  • This stack is used for executing native methods written in languages other than Java.
  • It supports the execution of code that interacts with the underlying operating system or native libraries.

 

Execution Engine

The execution engine is responsible for executing Java bytecode. It consists of multiple components that work together to convert bytecode into machine-specific instructions and execute them.

 

     1. Interpreter:

  • The interpreter reads the bytecode and executes instructions one by one.
  • While simple, it can be slower because each instruction is interpreted every time it is executed.

 

     2. Just-In-Time (JIT) Compiler:

  • The JIT compiler improves performance by compiling frequently executed bytecode into native machine code.
  • Once compiled, the native code is directly executed, significantly speeding up the execution.
  • The JIT compiler uses techniques like method-level compilation and on-stack replacement for better optimisation.

 

Example Code: Performance Improvement with JIT

public class JITExample { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { int square = i * i; } long end = System.currentTimeMillis(); System.out.println("Time taken: " + (end - start) + " ms"); } }

In this example, the JIT compiler will optimise the loop to improve execution speed.

     3. Garbage Collector (GC):

  • This is performed automatically to free up an object’s memory when no longer needed.
  • Garbage collectors (Serial, Parallel, CMS, G1) apply a sequence of algorithms that address memory organisation and performance optimisation.

Native Method Interface (JNI)

The Native Method Interface (JNI) is able to bridge the gap between Java code and applications or libraries written in C or C++. This ability proves useful in situations where there is a need to interface with platform-specific features or legacy systems.

 

  • With the help of JNI, Java can invoke native functions and utilise native libraries.
  • It makes it possible to work between Java and non-Java code without any difficulties.

Example Code: Using JNI

To use JNI, we create a native method in Java, implement it in C, and load the native library using System.loadLibrary in Java.

public class NativeExample { static { System.loadLibrary("nativeLib"); } // Declaration of native method public native void sayHello(); public static void main(String[] args) { new NativeExample().sayHello(); } }

Native Method Libraries

Native method libraries are loaded when a native method is called. These libraries allow Java programs to execute platform-specific operations that cannot be implemented in pure Java.

 

  • They are loaded through JNI, enabling access to system-level functionalities.
  • Native libraries are typically compiled as .dll (Windows), .so (Linux), or .dylib (macOS) files.

JVM Performance Optimisation

Optimising JVM performance ensures that Java applications run efficiently. This involves tuning JVM settings, using monitoring tools, and optimising code. Here’s how to achieve better JVM performance.

 

JVM Tuning Techniques

JVM tuning involves configuring settings like memory allocation, garbage collection, and JVM parameters to boost performance.

 

      1. Memory Tuning:

  • Set the initial (-Xms) and maximum (-Xmx) heap size to match the application’s needs.

      2. Selecting the Right Garbage Collector:

2.1 Use suitable garbage collection algorithms based on the application’s requirements:

  • Serial Garbage Collector: Ideal for single-threaded, small applications.
  • Parallel Garbage Collector: Suitable for multi-threaded applications prioritising throughput.
  • G1 Garbage Collector: Balances low pause times and throughput for large applications.
  • Configure the garbage collector using parameters like -XX:+UseG1GC.

        3. Tuning JVM Parameters:

  • Set options like -XX:MaxPermSize and -XX:+PrintGCDetails for better memory and garbage collection management. These settings fine-tune how resources are allocated and monitored.

Profiling and Monitoring Tools

Monitoring tools are essential for tracking JVM performance, enabling developers to identify and fix bottlenecks.

  • JVisualVM: A JDK tool for monitoring Java applications, showing heap usage, thread activity, and garbage collection data.
  • JConsole: Offers real-time metrics on memory, threads, and CPU load. Useful for monitoring JVM performance and managing JMX beans.
  • YourKit: A commercial profiler with advanced features for analysing memory and CPU usage. It helps detect performance issues, memory leaks, and optimise garbage collection.

Code Optimisation Techniques

Efficient coding practices play a significant role in JVM performance. Here are key techniques:

  • Avoiding Memory Leaks: Memory leaks happen when objects are kept in memory longer than needed due to lingering references. Use tools like JVisualVM to identify such leaks. Avoid static references and ensure objects are dereferenced when not in use.
  • Optimising Loops: Reduce the number of operations inside loops. Avoid repeatedly accessing collection sizes or calling methods that do not change within the loop.

Example:

// Inefficient for (int i = 0; i < myList.size(); i++) { ... } // Optimised int size = myList.size(); for (int i = 0; i < size; i++) { ... }
  • Choosing Appropriate Data Structures: Use the right data structures for the task: ArrayList for frequent random access, HashMap for quick lookups, and TreeMap if sorting is needed.
  • Avoiding Unnecessary Object Creation: Reuse objects when possible to reduce memory and garbage collection overhead. Avoid creating new objects within frequently executed methods or loops.

 

Also Read: HashMap in Java

Example: Running a Java Program in the JVM

Running a Java program involves several steps, from writing the code to executing it on the JVM. Here’s a simple example to demonstrate the process.

Simple Java Code Example

public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }

Compilation and Execution Steps

1.    Compilation

  • The Java source file (HelloWorld.java) is compiled using the javac compiler, which converts it into bytecode (HelloWorld.class).
  • The bytecode is platform-independent, meaning it can run on any system with a JVM.

javac HelloWorld.java

 

2.    Execution

  • The compiled bytecode (HelloWorld.class) is executed using the java command.
  • The JVM loads the bytecode, performs verification, and executes it.

java HelloWorld

Bytecode Execution Process

  • Class Loading: The JVM loads the HelloWorld class into memory using the ClassLoader.
  • Bytecode Verification: Confirms that the bytecode is legitimate and is compliant with Java safety procedures.
  • Just-in-time (JIT) Compilation: Translates the frequently appearing bytecode into machine language instructions so as to enhance execution speed.
  • Execution Engine: The engine reads the bytecode, interprets the actual program and contains the words ‘Hello World’ that they will display on the monitor.

Real-World Use Cases and Examples

Troubleshooting Memory Issues

 

An OutOfMemoryError or other memory issues arise when there is not sufficient heap space available.

 

Example: Memory Leak Detection

import java.util.ArrayList; import java.util.List; public class MemoryLeakExample { public static void main(String[] args) { List<String> list = new ArrayList<>(); while (true) { list.add("This will cause a memory leak"); } } }

The above code causes an OutOfMemoryError as it continuously adds data to the list without releasing memory.

 

  • Solution: Identify and fix memory leaks by removing unused references, using tools like JVisualVM, and adjusting JVM parameters.

Resolving High CPU Usage

High CPU usage can be linked to inefficient code or poor garbage collection settings. Profiling tools help pinpoint bottlenecks.

Example: Inefficient Loop Causing High CPU

public class HighCPUExample { public static void main(String[] args) { while (true) { // Inefficient loop consuming CPU System.out.println("Busy loop"); } } }
  • Solution: Optimise code by reducing unnecessary computations and setting proper sleep intervals for busy loops.

Common JVM Errors and Exceptions

Several common errors can occur while running Java programs. Understanding their causes and solutions helps in troubleshooting.

Common Errors

 

OutOfMemoryError

This occurs when the JVM cannot allocate enough memory. This can be due to memory leaks, large data structures, or insufficient heap size.

 

Example Trigger:

int[] largeArray = new int[Integer.MAX_VALUE];

Solution: Increase heap size (-Xmx), optimise memory usage, or use a different garbage collector.

 

StackOverflowError

This Happens when the stack size is exceeded, typically due to deep or infinite recursion.

Example Trigger:

public class StackOverflowExample { public static void main(String[] args) { recursiveMethod(); } public static void recursiveMethod() { recursiveMethod(); // Infinite recursion } }
  • Solution: Refactor the code to avoid deep recursion or increase the stack size (-Xss).

 

ClassNotFoundException

Thrown when the JVM cannot find the specified class during dynamic loading.

Example Trigger:

 

Class.forName(“com.nonexistent.Class”);

 

  • Solution: Ensure the classpath includes all required classes.

 

Avoiding Common Errors

  • Use Proper Memory Management: Monitor heap usage and adjust JVM settings.
  • Optimise Code to Avoid Deep Recursion: Use iterative methods when possible.
  • Ensure Correct Classpath Configuration: Verify that all dependencies are correctly set up.

 

Also Read: Java Interview Questions and Answers

Conclusion

Developers who want to deal with performance problems and optimise their applications must have knowledge of the architecture of the JVM. The importance of the JVM in memory, garbage collection and execution makes it essential to know how it works internally.

 

JVM settings can be enhanced by understanding fundamental principles like class loading, parameters of memory zones, and garbage collection which in turn will enhance Java developers’ coding ways. This knowledge also ensures that Java programs are well-optimised and the development process is carried out in the most efficient way possible. Try the Certificate Program in Application Development by Hero Vired to explore Java in detail

FAQs
The JVM is a virtual machine that executes Java bytecode on different platforms.
It helps in optimising performance and troubleshooting Java applications.
Key components include the ClassLoader, Memory Area, Execution Engine, and JNI.
JIT converts frequently executed bytecode into native code for faster performance
It automatically reclaims memory from objects that are no longer in use.
It occurs when the JVM can't allocate enough memory, often due to leaks or large data structures.
Use tools like JVisualVM, JConsole, or YourKit for monitoring and profiling.

Updated on October 23, 2024

Link
left dot patternright dot pattern

Programs tailored for your success

Popular

Management

Data Science

Finance

Technology

Future Tech

Upskill with expert articles

View all
Hero Vired logo
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.
Blogs
Reviews
Events
In the News
About Us
Contact us
Learning Hub
18003093939     ·     hello@herovired.com     ·    Whatsapp
Privacy policy and Terms of use

|

Sitemap

© 2024 Hero Vired. All rights reserved