Java class loading and reflection are fundamental to the runtime flexibility of the JVM. Class loading follows a lazy, on-demand principle: classes are loaded into memory only when they are first needed.
Class Lifecycle
A class in the JVM undergoes seven stages from loading to卸载 (unloading): Loading, Verification, Preparation, Resolution, Initialization, Using, and Unloading. The first five stages are sequential except for Resolution, which may occur after Initialization to support dynamic binding.
When Class Loading Occurs
Active references trigger immediate class initialization:
- Creating a new instance (
new) - Accessing static fields or invoking static methods
- Using reflection (e.g.,
Class.forName()) - Initializing a subclass (which triggers parent initialization first)
- Starting the JVM with the main class
Passive references do NOT trigger initialization:
- Referencing a parent’s static field via a subclass
- Defining an array of the class
- Referencing a
static finalconstant (a compile-time constant)
Phases of Class Loading
-
Loading: Obtain the binary byte stream of a class by its fully qualified name, transform the static storage representation into a runtime structure in Metaspace, and create a
java.lang.Classobject as the access entry. -
Verification: Ensure the byte stream is structurally correct and safe (format, metadata, bytecode, and symbolic reference checks).
-
Preparation: Allocate memory for static variables and set default zero values (except for
finalstatics). Instance variables are not yet allocated. -
Resolution: Replace symbolic references in the constant pool with direct references (pointers, offsets, or handles).
-
Initialization: Execute the
<clinit>()method generated by the compiler, which merges all static assignments and static blocks.
Class Loaders
Class loading is delegated through a hierarchical parent-delegation model:
- Bootstrap ClassLoader: Loads core Java libraries (
rt.jar). Implemented natively, not a subclass ofClassLoader. - Extension ClassLoader: Loads classes from the extension directory (
<JRE_HOME>/lib/ext). Inheritsjava.net.URLClassLoader. - Application ClassLoader: Loads appilcation classes from the classpath. Extends
java.lang.ClassLoader. - Custom ClassLoaders: Developers can define their own loaders for specialized strategies.
What Is Reflection
Reflection enables a program to inspect and manipulate its own structure at runtime. It allows you to:
- Retrieve class metadata (name, package, fields, methods, annotations, type, class loader)
- Dynamically create instances
- Invoke any method (including private ones)
- Access and modify fields (including private ones)
- Determine the runtime class of an object
Common use cases: dynamic proxies, framework development (Spring, Hibernate), performance optimization, serialization/deserialization tools, hot deployment, unit testing.
Caveats:
- Performance: Reflection is slower due to extra type and security checks.
- Security: It can break encapsulation by accessing private members.
- Maintainability: Overuse makes code harder to read and debug.
How Reflection Works Under the Hood
The core of reflection is the Class object, which is generated when a class is loaded. Steps:
- Obtain a
Classobject (e.g., viaClass.forName(),.class, orgetClass()). - Use the
Classobject to retrieveField,Method,Constructorobjects. - Manipulate them (create instances, invoke methods, get/set fields). For private members, call
setAccessible(true)to bypass access controls.
The JVM reads the bytecode of a .class file and constructs the Class object in Metaspace, which serves as the gateway for all reflection operations.
Getting a Class Object: Three Ways
1. Class.forName(String className)
Dynamically loads a class by its fully qualified name and triggers initialization.
Class<?> cls = Class.forName("java.lang.String");
System.out.println(cls.getName()); // java.lang.String
Recommended for reflection.
2. ClassName.class
Compile-time type reference; does not trigger initialization (unless the class is not yet loaded).
Class<String> cls = String.class;
System.out.println(cls.getName()); // java.lang.String
Tight dependency on import.
3. object.getClass()
Called on an existing instance; does not trigger re-initialization.
String str = "Hello";
Class<?> cls = str.getClass();
System.out.println(cls.getName()); // java.lang.String
But if you already have an instance, reflection is often unnecessary.
Advantages and Disadvantages
Advantages:
- Higher flexibility and extensibility—code can work with unknown classes at compile time.
- Simplifies framework development by automating object creation and dependency management.
- Enhances testing and debugging by accessing private internals.
Disadvantages:
- Performance overhead from runtime type checking and security navigation.
- Security risks from breaking encapsulation.
- Reduced readability and maintainability.
- Fragile code—changes in class structure (renaming methods, changing signatures) can break reflective calls silently.
Practical Applications of Reflection
- Framework Design: Load and wire components dynamically based on configuration or annotations.
- Unit Testing: Access private fields/methods to verify internal state.
- Dynamic Proxies: Generate proxy objects at runtime for AOP (e.g., transaction management, logging).
- Serialization/Deserialization: Tools like Jackson or Gson use reflection to map objects to/from JSON or XML.
- Plugin Systems: Load plugins from external JARs without compile-time dependencies.
- Cross-Language Interop: Use reflection with JNI to invoke native methods with dynamic parameters.
While reflection is powerful, it should be used judiciously, balancing flexibility against performance and maintainability costs.