Java Virtual machine (JVM) is the virtual machine that runs the Java bytecodes. You get this bytecode by compiling the .java files into .class files. .class files contain the bytecodes understood by the JVM.
In the real world, JVM is a specification that provides a runtime environment in which Java bytecode can be executed. Different vendors provide different implementations of this specification. For example, this wiki page lists down different JVM implementations.
Most popular implementation of JVM is Hotspot which is owned and provided by Oracle Corporation. (Previously by Sun Microsystems, Inc.).
JVM delivers the optimal performance for Java applications using many advanced techniques, incorporating a state-of-the-art memory model, garbage collector, and adaptive optimizer.
JVM comes in two different flavors – client and server. Although the Server and the Client VMs are similar, the Server VM has been specially tuned to maximize peak operating speed. It is intended for executing long-running server applications, which need the fastest possible operating speed more than a fast start-up time or smaller runtime memory footprint. Developers can choose which system they want by specifying -client or -server.
The JVM is called virtual because it provides a machine interface that does not depend on the underlying operating system and machine hardware architecture. This independence from hardware and the operating system is a cornerstone of the write-once-run-anywhere value of Java programs.
2.1. JVM Architecture
2.1.1. Class Loader
The class loader is a subsystem used for loading class files. It performs three major functions i.e. class loading, linking, and initialization.
To load classes, JVM has 3 kind of class loaders. Bootstrap, extension and application class loader.
When loading a class file, JVM finds out a dependency for some arbitrary class XYZ.class.
First bootstrap class loader tries to find the class. It scans the rt.jar file in JRE lib folder.
If class is not found then extension class loader searches the class file in inside jre\lib\ext folder.
Again if class is not found then application classloader searches all the Jar files and classes in CLASSPATH environment variable of system.
If class is found by any loader then class is loaded by class loader; else ClassNotFoundException is thrown.
After class is loaded by the classloader, linking is performed. A bytecode verifier will verify whether the generated bytecode is proper or not if verification fails we will get a verification error. It also performs the memory allocation to static variables and methods found in the class.
This is the final phase of class loading, here all static variable will be assigned with the original values and the static blocks will be executed.
2.1.2. JVM Memory Areas
Memory area inside JVM is divided into multiple parts to store specific parts of application data.
Method Area stores class structures like metadata, the constant runtime pool, and the code for methods.
Heap stores all objects that are created during application execution.
Stacks store local variables, and intermediate results. All such variables are local to the thread by which they are created. Each thread has its own JVM stack, created simultaneously as the thread is created. So all such local variable are called thread-local variables.
PC register store the physical memory address of the statements which is currently executing. In Java, each thread has its separate PC register.
Java supports and uses native code as well. Many low level code is written in languages like C and C++. Native method stacks hold the instruction of native code.
2.2. JVM Execution Engine
All code assigned to JVM is executed by an execution engine. The execution engine reads the byte code and executes one by one. It uses two inbuilt interpreter and JIT compiler to convert the bytecode to machine code and execute it.
Platform Specific Interpreters
Platform Specific Interpreters
With JVM, both interpreter and compiler produce native code. The difference is in how they generate the native code, how optimized it is as well how costly the optimization is.
A JVM interpreter pretty much converts each byte-code instruction to corresponding native instruction by looking up a predefined JVM-instruction to machine instruction mapping. It directly executes the bytecode and does not perform any optimization.
2.2.2. JIT Compiler
To improve performance, JIT compilers interact with the JVM at runtime and compile appropriate bytecode sequences into native machine code. Typically, JIT compiler takes a block of code (not one statement at a time as interpreter), optimize the code and then translate it to optimized machine code.
The JIT compiler is enabled by default. You can disable the JIT compiler, in which case the entire Java program will be interpreted. Disabling the JIT compiler is not recommended except to diagnose or work around JIT compilation problems.