Lab 01: JVM Internals

Time: 60 minutes | Level: Architect | Docker: docker run -it --rm zchencow/innozverse-java:latest bash


Overview

Understand the JVM's class loading subsystem — the delegation model, custom ClassLoaders, bytecode inspection, and runtime flags that control JIT compilation diagnostics. This is the foundation for dynamic proxies, plugin systems, OSGi, and bytecode instrumentation.


Step 1: The ClassLoader Hierarchy

Java uses a parent-delegation model: every ClassLoader delegates to its parent before attempting to load a class itself.

Bootstrap ClassLoader  (loads rt.jar / java.* — null in Java)

Platform ClassLoader   (formerly Extension ClassLoader — loads javax.*)

Application ClassLoader (loads -classpath, user code)

Custom ClassLoaders    (plugins, OSGi, dynamic bytecode)
public class ClassLoaderHierarchy {
    public static void main(String[] args) throws Exception {
        ClassLoader app = ClassLoaderHierarchy.class.getClassLoader();
        ClassLoader platform = app.getParent();
        ClassLoader bootstrap = platform.getParent(); // null — native Bootstrap

        System.out.println("App ClassLoader:      " + app);
        System.out.println("Platform ClassLoader: " + platform);
        System.out.println("Bootstrap ClassLoader:" + bootstrap); // null

        // Delegation: String is loaded by Bootstrap
        Class<?> strClass = app.loadClass("java.lang.String");
        System.out.println("String loaded by:     " + strClass.getClassLoader()); // null
    }
}

💡 null for a ClassLoader means the Bootstrap ClassLoader — it's implemented in native C++, not Java.


Step 2: Custom ClassLoader with defineClass

💡 Two classes are equal only if they have the same name and were loaded by the same ClassLoader. This enables plugin isolation.


Step 3: Generating Bytecode with ASM

ASM is the industry standard for bytecode manipulation used by Spring, Hibernate, and Mockito.

pom.xml dependency:

💡 COMPUTE_FRAMES tells ASM to automatically calculate stack map frames — mandatory for Java 7+.


Step 4: Inspecting Bytecode with javap

Sample javap output for increment():


Step 5: JVM Diagnostic Flags

💡 The format: [timestamp] [compile_id] [flags] [class::method] [size] [level] Flag % = OSR (on-stack replacement), ! = has exception handler.


Step 6: ClassLoader Isolation in Practice


Step 7: Runtime Class Generation and Loading


Step 8: Capstone — Custom ClassLoader with Delegation

Put it all together: a custom ClassLoader that intercepts loading, logs delegation decisions, and can define classes from in-memory bytecode.

📸 Verified Output:


Summary

Concept
Key Class/API
Use Case

Bootstrap ClassLoader

null

Loads java.* from JDK

Platform ClassLoader

ClassLoaders$PlatformClassLoader

Loads javax.*, modules

App ClassLoader

ClassLoaders$AppClassLoader

User classpath

Custom ClassLoader

ClassLoader.defineClass()

Plugin isolation, dynamic code

ASM bytecode gen

ClassWriter, MethodVisitor

Proxies, instrumentation

javap

javap -c -verbose

Bytecode debugging

JIT flags

-XX:+PrintCompilation

Performance analysis

Parent delegation

loadClass() loop

Security, consistency

Last updated