Compiler and JVM Architecture with Reflection API

Java 14 min min read Updated: Mar 31, 2026 Intermediate
Compiler and JVM Architecture with Reflection API
Intermediate Topic 15 of 25

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.

Key Concept: The Java compiler converts source code into bytecode, the JVM loads and executes that bytecode, and the Reflection API allows programs to inspect and use classes, methods, fields, and constructors dynamically 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:

bash javac FileName.java

Example:

bash javac Hello.java

After compilation:

text Hello.class

This .class file contains bytecode, which is platform independent and can run on any system that has a JVM.

Source Code to Bytecode Example

java class Hello { public static void main(String[] args) { System.out.println("Hello Java"); } }

Compile:

bash javac Hello.java

Run:

bash java Hello

Here:

  • Hello.java → source code
  • javac → compiler
  • Hello.class → bytecode
  • java 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:

java Student s = new Student();

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:

java void show() { int x = 10; }

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

  1. Program is written in .java file
  2. Compiler converts it into .class file
  3. Class loader loads class into JVM
  4. Bytecode is verified and linked
  5. Execution engine runs bytecode
  6. Objects are managed in heap and unused ones are removed by garbage collector
Execution Flow: Source Code → Compiler → Bytecode → Class Loader → JVM Memory Areas → Execution Engine → Program Output

Practical Example of Compilation and Execution

java class Demo { static int x = 10; public static void main(String[] args) { int y = 20; System.out.println(x + y); } }

What Happens Internally?

  • javac compiles source code into bytecode
  • Class loader loads Demo.class
  • Static variable x goes into method area
  • Local variable y goes 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

java Class c = Student.class;

2. Using getClass()

java Student s = new Student(); Class c = s.getClass();

3. Using Class.forName()

java Class c = Class.forName("Student");

Reflection Example: Getting Class Name

java class Student { } public class Main { public static void main(String[] args) { Student s = new Student(); Class c = s.getClass(); System.out.println(c.getName()); } }

Output

text Student

Reflection Example: Getting Methods

java import java.lang.reflect.Method; class Student { public void show() { } public void display() { } } public class Main { public static void main(String[] args) { Class c = Student.class; Method[] methods = c.getDeclaredMethods(); for (Method m : methods) { System.out.println(m.getName()); } } }

This program prints the declared method names of the class.

Reflection Example: Getting Fields

java import java.lang.reflect.Field; class Student { int age; String name; } public class Main { public static void main(String[] args) { Class c = Student.class; Field[] fields = c.getDeclaredFields(); for (Field f : fields) { System.out.println(f.getName()); } } }

Reflection Example: Invoking a Method Dynamically

java import java.lang.reflect.Method; class Student { public void show() { System.out.println("Method invoked through reflection"); } } public class Main { public static void main(String[] args) throws Exception { Student s = new Student(); Class c = s.getClass(); Method m = c.getDeclaredMethod("show"); m.invoke(s); } }

Here, the method is not called directly with s.show(). Instead, it is invoked dynamically using reflection.

Reflection Example: Accessing Constructor

java import java.lang.reflect.Constructor; class Student { Student() { System.out.println("Constructor called"); } } public class Main { public static void main(String[] args) throws Exception { Class c = Student.class; Constructor cons = c.getDeclaredConstructor(); Object obj = cons.newInstance(); } }

Accessing Private Members with Reflection

Reflection can even access private fields and methods using setAccessible(true). This is powerful but should be used carefully.

java import java.lang.reflect.Field; class Student { private int age = 20; } public class Main { public static void main(String[] args) throws Exception { Student s = new Student(); Class c = s.getClass(); Field f = c.getDeclaredField("age"); f.setAccessible(true); System.out.println(f.get(s)); } }

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

  • javac compiles 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.

Quick Summary: The compiler converts Java source code into bytecode, the JVM loads and executes that bytecode using its internal architecture, and the Reflection API enables runtime inspection and dynamic usage of classes, methods, fields, and constructors.

Get Newsletter

Subscibe to our newsletter and we will notify you about the newest updates on Edugators