Compiler and JVM Architecture with Reflection API
One of the most important strengths of Java is that it does not directly compile source code into platform-specific machine code like many traditional languages. Instead, Java follows a multi-step execution model using the compiler and the Java Virtual Machine (JVM). This design is the main reason Java is platform independent.
To become strong in Core Java, it is very important to understand how Java source code is converted into bytecode, how the JVM loads and executes that bytecode, and how advanced features like the Reflection API allow a program to inspect and interact with classes at runtime.
What is the Java Compiler?
The Java compiler is a tool that translates Java source code into bytecode. The source code is written in files with the .java extension, and after compilation it becomes a .class file.
The Java compiler command is:
Example:
After compilation:
This .class file contains bytecode, which is platform independent and can run on any system that has a JVM.
Source Code to Bytecode Example
Compile:
Run:
Here:
Hello.java→ source codejavac→ compilerHello.class→ bytecodejava Hello→ JVM executes bytecode
What is Bytecode?
Bytecode is an intermediate instruction set generated by the Java compiler. It is not machine code for a specific CPU or operating system. Instead, it is a standard format understood by the JVM.
This is the reason Java follows:
Write Once, Run Anywhere
The same bytecode can run on Windows, Linux, macOS, or any other environment where a suitable JVM is available.
What is JVM?
JVM stands for Java Virtual Machine. It is a virtual execution environment that runs Java bytecode.
JVM acts like an engine between Java bytecode and the underlying operating system.
Its main responsibilities are:
- loading class files
- verifying bytecode
- managing memory
- executing bytecode
- handling garbage collection
- providing runtime environment
JDK, JRE, and JVM
These three terms are often asked in interviews and are closely related.
| Term | Meaning | Purpose |
|---|---|---|
| JDK | Java Development Kit | Used for developing Java applications |
| JRE | Java Runtime Environment | Used for running Java applications |
| JVM | Java Virtual Machine | Executes Java bytecode |
Relationship:
- JDK contains JRE
- JRE contains JVM
High-Level JVM Architecture
The JVM has several important internal components. A simplified architecture includes:
- Class Loader Subsystem
- Runtime Data Areas
- Execution Engine
- Native Method Interface
- Native Method Libraries
1. Class Loader Subsystem
The class loader subsystem is responsible for loading the required .class files into memory when they are needed.
It performs three main activities:
- Loading
- Linking
- Initialization
Loading
The class loader reads the class file and loads the class data into memory.
Linking
Linking includes:
- Verification → checks whether bytecode is valid
- Preparation → allocates memory for static variables
- Resolution → replaces symbolic references with actual references
Initialization
In this phase, static variables are assigned actual values and static blocks are executed.
Types of Class Loaders
JVM mainly uses three class loaders:
- Bootstrap Class Loader → loads core Java classes like
java.lang.* - Extension Class Loader → loads extension libraries
- Application Class Loader → loads user-defined classes from classpath
2. Runtime Data Areas
JVM creates different memory areas during execution. Some are shared across all threads, while some are thread-specific.
Shared Areas
- Method Area
- Heap
Thread-Specific Areas
- Java Stack
- PC Register
- Native Method Stack
Heap Area
Heap is the runtime memory area where objects and instance variables are stored. It is shared by all threads.
Example:
The object created by new Student() is stored in heap memory.
Method Area
Method area stores class-level data such as:
- class metadata
- method code
- static variables
- constant pool
This area is shared by all threads.
Java Stack
Each thread has its own Java stack. It stores method calls, local variables, and partial results.
Every method call creates a new stack frame.
Example:
Here, local variable x is stored in the stack frame of show().
PC Register
PC stands for Program Counter. Each thread has its own PC register that stores the address of the current instruction being executed.
Native Method Stack
This stack is used for methods written in native languages like C or C++ and invoked through JNI.
3. Execution Engine
The execution engine is responsible for executing the bytecode loaded into memory.
It mainly includes:
- Interpreter
- JIT Compiler
- Garbage Collector
Interpreter
The interpreter reads and executes bytecode instruction by instruction. This is simple but can be slow for repeated code.
JIT Compiler
JIT stands for Just-In-Time Compiler. It improves performance by converting frequently used bytecode into native machine code at runtime.
This makes repeated execution faster.
Garbage Collector
Garbage collector automatically removes unused objects from heap memory, helping with automatic memory management.
4. Native Method Interface (JNI)
JNI stands for Java Native Interface. It allows Java code to call native methods written in non-Java languages such as C or C++.
5. Native Method Libraries
These are external libraries used by native methods, usually platform-specific files like DLL or SO files.
Simple JVM Architecture Flow
- Program is written in
.javafile - Compiler converts it into
.classfile - Class loader loads class into JVM
- Bytecode is verified and linked
- Execution engine runs bytecode
- Objects are managed in heap and unused ones are removed by garbage collector
Practical Example of Compilation and Execution
What Happens Internally?
javaccompiles source code into bytecode- Class loader loads
Demo.class - Static variable
xgoes into method area - Local variable
ygoes into stack memory System.out.println()executes through JVM
What is Reflection API?
Reflection API in Java allows a program to inspect and manipulate classes, methods, fields, and constructors at runtime.
Normally, Java code works with classes in a fixed compile-time manner. Reflection allows Java code to discover information dynamically while the program is running.
Using reflection, you can:
- get class name at runtime
- list methods and fields
- access constructors dynamically
- invoke methods dynamically
- modify private fields (with care)
Why Reflection API is Used
Reflection is useful in advanced Java programming, especially in:
- framework development
- dependency injection
- ORM tools like Hibernate
- testing frameworks
- annotation processing
- dynamic object creation
Class Class in Reflection
Reflection mainly starts with the Class class. It represents metadata about a Java class.
Ways to Get Class Object
1. Using class literal
2. Using getClass()
3. Using Class.forName()
Reflection Example: Getting Class Name
Output
Reflection Example: Getting Methods
This program prints the declared method names of the class.
Reflection Example: Getting Fields
Reflection Example: Invoking a Method Dynamically
Here, the method is not called directly with s.show(). Instead, it is invoked dynamically using reflection.
Reflection Example: Accessing Constructor
Accessing Private Members with Reflection
Reflection can even access private fields and methods using setAccessible(true). This is powerful but should be used carefully.
This allows reading a private field dynamically.
Advantages of Reflection API
- supports dynamic programming
- useful for frameworks and libraries
- helps inspect classes at runtime
- makes dependency injection and ORM possible
- allows dynamic method invocation
Disadvantages of Reflection API
- slower than direct method calls
- reduces type safety
- can break encapsulation
- code becomes harder to understand and debug
- should not be overused in normal business logic
Real-World Use Cases of Reflection
- Spring Framework creating beans dynamically
- Hibernate inspecting entity classes
- JUnit finding and running test methods
- serialization libraries inspecting fields
- annotation-based programming
Common Mistakes
- Confusing JDK, JRE, and JVM
- Thinking bytecode is machine code
- Assuming JVM memory areas are all the same
- Using reflection unnecessarily in simple code
- Not handling checked exceptions in reflection code
Best Practices
- Understand compiler and JVM flow before moving to advanced topics
- Use reflection only when dynamic behavior is truly needed
- Avoid excessive reflective access to private members
- Write clean and structured code even when using reflection
- Know JVM memory areas clearly for interviews and performance understanding
Interview-Oriented Points
javaccompiles source code into bytecode- JVM executes bytecode, not source code directly
- Class loader loads classes into JVM memory
- Heap stores objects, stack stores local variables and method frames
- Method area stores class-level information and static members
- JIT improves performance by compiling bytecode into native code at runtime
- Reflection API allows inspection and manipulation of classes at runtime
Conclusion
The Java compiler and JVM architecture are the core of Java’s platform-independent design. Understanding how Java source code becomes bytecode and how the JVM loads, manages, and executes that bytecode is essential for every serious Java developer.
The Reflection API adds another advanced capability by allowing Java programs to inspect and use classes dynamically at runtime. Together, these topics provide deep insight into how Java works internally and why it is so powerful in enterprise and framework-based development.

