The Java Virtual Machine, or JVM, is a key component of the Java programming language, providing a platform-independent environment for the execution of Java code across most major hardware, operating systems, and software architectures. In this programming tutorial, we discuss what the JVM is, how it works, and its key features for developers.
What is the JVM?
The Java Virtual Machine is a virtual “machine” that provides a runtime environment for Java applications and programs. Its role is simple: interpret and execute Java bytecode, which a low-level representation of the compiled form of a piece of Java code. When applications written in Java are compiled, they produce bytecode, which can be executed (or run) by any JVM implementation, regardless of the underlying architecture, hardware, or operating system.
Java’s JVM is platform-independent, meaning Java programs can be written once and run on any JVM implementation – a principle known as WORA or write once, run anywhere. This concept of WORA is achieved courtesy of a layer of abstraction residing between the Java code and the underlying OS and hardware. At runtime, the JVM interprets the resultant bytecode and translates it into native machine code, accounting for the characteristics of the underlying system it will run on.
How Does the Java Virtual Machine Work?
To answer the question, “How does the JVM work?” developers must first understand the steps the JVM follows in order to interpret Java code and then execute it. These steps include:
- Loading the bytecode
- Verifying the bytecode
- Preparing memory resources
Interpreting the Java bytecode
- Just-in-time Compilation
- Garbage Collection
Loading Java Bytecode
The very first step that occurs in the JVM process involves loading the Java bytecode into the JVM. This task is performed by the class loader, whose responsibility is to locate any necessary bytecode files and load them into system memory.
After bytecode is loaded into memory, the JVM needs to verify its correctness, which it does by checking the Java bytecode for violations of the Java language specification, including illegal access to private fields or private methods.
Once the bytecode is verified, the Java Virtual Machine preps the memory and resources needed in order for the program to execute. This preparation includes memory allocation for any required objects and initializing static variables.
Interpretation Java Bytecode
Next, the JVM has to interpret the bytecode and sequentially execute each instruction. While each instruction is executed, the JVM maintains a stack of values to be used by any following sets of instructions.
Once the code has been interpreted, the JVM may uses Just-In-Time (JIT) compilation to improve performance. During JIT compilation, the JVM compiles frequently executed bytecode into native machine language, which is executed with more efficiency than interpreted bytecode. We discuss JIT in more detail in a section below.
As the application is executing, the Java Virtual Machine manages memory resources (allocating and deallocating) by performing automatic garbage collection. Garbage collection frees up memory resources that are no longer being used by the program or CPU, allowing the memory to be reclaimed by the JVM and put to other uses.
Read: Java Best Practices
What are the Features of the JVM?
The Java Virtual Machine has several key features that make it such a powerful and invaluable platform including platform independence, memory management, security, dynamic loading, JIT compilation, and multithreading:
- Platform independence: The JVM includes a platform-independent runtime environment which allows for the execution of Java applications on most hardware and software architectures.
- Memory management: The JVM manages memory automatically vis its built-in garbage collector, which frees up memory when the program is finished with it, saving the developer from having to account for memory allocation and deallocation.
- Security: The JVM enforces security policies to prevent malicious code from gaining access to system resources.
- Dynamic loading: With the Java Virtual Machine, classes are dynamically loaded at runtime, making it so developers can extend the functionality of their Java programs without the need for recompilation or redeployment.
- Just-In-Time compilation: JIT compilation improves application performance by compiling frequently executed bytecode into native machine language.
- Multithreading: The JVM supports multithreading, which lets Java programs run multiple threads of execution at the same time.
What is JIT?
As discussed, JIT is a compilation mechanism that lets the JVM compile frequently executed bytecode into native machine language during runtime. During the execution of a Java application, the JVM interprets the bytecode and executes it one instruction at a time. This process can be slow and not optimal, which is where JIT comes into play.
Just-in-Time compilation improves this performance by compiling frequently executed bytecode dynamically, changing it into native machine code, which is much more efficient than its interpreted counterpart. This native code gets stored in memory and is reused whenever the same code gets executed, which further improves application performance.
How Does JIT Work?
As stated, as a Java program gets executed, the JVM interprets the bytecode and executes it, sequentially, one instruction at a time. During this process, if the JVM detects that a section of code is being executed often, it begins to compile that frequently used code into native machine code.
The JIT compiler can optimize compiled code performance through the use of several techniques. For instance loop unrolling may be employed to eliminate the overhead of loop control structures. Inline methods may be used to eliminate the overhead associated with method calls.
What are the Benefits of JIT?
JIT provides several benefits to Java developers, including improved performance, faster start-up times, adaptive optimization, platform independence, and shared compiled code:
- Improved performance: Improve performance by dynamically compiling frequently executed bytecode into native machine code.
- Faster startup times: Since the JVM only compiles frequently executed code, startup times for Java applications are a lot faster.
- Adaptive optimization:
JIT compilers adapt code optimization strategies depending on the execution profile of the particular program, which help improves performance.
- Platform independence: JIT compilation lets Java programs run on many different hardware and software systems, without needing to recompile.
- Code sharing: Since compiled code gets stored in memory and is reused upon subsequent executions, JIT allows multiple instances of the same program to share compiled code. This action helps reduce memory usage and resources.
Final Thoughts on the Java Virtual Machine and JIT
In this programming tutorial, we looked at the Java Virtual Machine, or JVM, and how it works, its features, and its benefits. This included providing a platform-independent runtime environment for Java programs to run, regardless of the system they are deployed on. The JVM interprets Java bytecode and translates it into native machine code at runtime.
We also discussed JIT compilation, which lets the JVM improve the performance of Java applications by dynamically compiling frequently executed bytecode into native machine code. This technique improves performance, reduces startup times, employs adaptive optimization, provides platform independence, and allows for code sharing.