A brief introduction to the JVM
As per the latest stats, there are around 10 million Java developers around the world.
I believe it’s the most common thought process of the developers to primarily focus on the features, functionality, and writing readable & understandable code. But performance is not a primary focus until a bug is encountered. At that time it seems like a completely dark art to improve the performance of the application. Hence I believe we should grasp a brief on the internals of the JVM to understand the performance aspects of the application.
JVM consists of the following major components
- Runtime memory area
- Execution Engine
The classloader is a chain of class loaders that are responsible for loading a class
- Bootstrap class loader — responsible for loading the core java runtime ( which is present in rt.jar before java 8 and from java 9 this has been modularized)
- Extension class loader
- Application class loaders — responsible for loading the classes from your application.
- When a child class can’t find a class it delegates the behavior to the parent class. In the end, if no class is able to find the class then it throws the error “ClassNotFound”
- A class is loaded only once and an object for it is being created in the runtime environment by the JVM.
BYTE CODE EXECUTION
Java compiler reads a java file and converts it into byte code.
Byte code is an intermediate representation and not tied to a specific machine-level architecture. This is the primary reason for the java portability.
ANATOMY OF A CLASS FILE
A class file consists of the following components
- Magic Number
- Version — Major and minor version of the class
- Constants — Constants of the class
- Access flag — Decides if the class is abstract, static, etc.
- This class — Refers to the name of the class
- Superclass — Name of the superclass
- Interfaces — Name of the interfaces implemented by the class
- Fields — Any fields in the class
- Methods — Any methods in the class
- Attributes — Any attributes of the class e.g. name of the source file, etc.
Acronym to remember — My Very Cute Animal Turns Savage In Full Moon Areas.
When a class is being loaded, the class loader in JVM checks for the version of the class if it’s compatible with the current version of the JVM. In case of incompatibility, JVM throws the error-
Hotspot in JVM
In 1999, Sun presented the HotSpot VM ( a key feature in JVM) that enabled Java performance as comparable (or better than) languages such as C and C++.
There are predominantly two classes of beliefs in lang design
- Close to metal and zero-cost abstraction (C/C++ are based on this principle)
- Favor developer productivity and get things done. (Java is based on this principle)
C++ enforces the zero-overhead principle and concentrates on pay for what you use. But it adds on a limitation on the developer to be meticulously aware of the OS system and internal working of the computers to create an efficient system.
C++ utilizes AOT (Ahead-of-time) compilation — yield the machine level code at compilation time and consequently, interpreter or JVM can be very lightweight.
Prematurely Java didn’t follow the ideology of zero overhead. Albeit hotspot VM assimilates this strategy to analyze applications’ runtime behavior. Then JVM generates the optimized code to enrich the performance of the application.
Hotspot examines the code behavior and enables the JIT compiler to generate the optimized machine-level code
Java interpreter executes each byte code instruction on the JVM stack machine in interpreted mode. The primary pursuit is to achieve code portability. Whereas to attain ultimate performance benefits the code should directly be executed on the CPU.
Hotspot achieves it utilizing the JIT compiler by transforming byte code into machine-level code to execute it directly on the CPU. It follows the below process for the same
- JIT watches the application while it’s running in the interpreted mode.
- Identify the part of the byte code that is executed with high frequency.
- Once the execution of a method surpasses a threshold limit, JIT will transform that piece of code into machine-level code and store it in the cache for future optimizations.
The immediate benefit of JIT is those optimization judgments based on the runtime info accumulated at the interpreted phase.
On the other hand, C++ performance is more predictable but at the expense of exposing the low-level complexity to the users. Additionally, the predicate doesn’t indicate that it has more satisfactory performance.
Code generated by AOT compilers run across a broad spectrum of processors and optimizations can’t be guaranteed across all.
Albeit, profile-guided optimizations (PGO) like in Java, can identify the precise CPU type during its runtime. It can facilitate the optimization precise to that individual processor.
Collectively all the above concepts are known as the Java Execution Engine. Components of the JVM Execution engine are
- JIT compiler
Memory Management (JMM)
In C/C++ lang developer is principally accountable for memory allocation and management that results in deterministic performance and the ability to control the lifetime of the object. Although it can cause memory leak problems and the developer needs to be mindful every time he is allotting the memory.
Java abstracted out the memory management specifically to focus the developer on the application by introducing the garbage collection (GC). GC is a nondeterministic process that triggers to collect back unused memory. It operates for a slight interval of time but during its execution, it generally pauses the application which puts the application under pressure that if GC execution time increases it will impact the application performance.
Major components of the JVM memory area
- It is the place where the actual object in java is created.
- -XMS indicates the minimum size of the heap.
- -XMS indicates the maximum size of the heap.
- It is also known as perm space and from java 8 this space is moved to OS native memory known as metaspace.
- It allocates the space for the class definition and the static variables.
- -XX: PermSpaceSize refers to method area size and the default size is 64 MB
- PC register is used to track the next instructed need to be executed by the JVM interpreter.
- There will be one PC register per thread.
- When a method invokes another method, these methods and space allocated by them are being allocated in Java Stack.
- Currently executing method will be at the top of the java stack.
- -XSS to maintain the stack size
NATIVE METHOD STACK
- Native method stack is similar to java method stack except it is used for the native calls made by the JVM.