Day 02 · Week 1 · Foundations

How Java actually runs.

Yesterday we said Java compiles to bytecode and the JVM runs it. Today we open that black box — just enough that no Spring 'magic' ever feels like real magic again.

~22 min readminimal codedemystifies Spring later
We are not becoming JVM engineers. We just want to remove fear. By the end of today, when Spring loads classes dynamically or generates proxies at runtime, you will recognize what's happening — not be surprised by it.

Why this chapter exists

Most Java courses skip this entirely or drown you in 50 pages of memory areas. Both are wrong. The truth is: Spring, Hibernate, JPA, and every annotation-driven framework rely on a handful of JVM features. If you don't see those features once, the frameworks always feel like sorcery.

The features we need to understand are exactly four:

  1. The compilation pipeline — how .java becomes running instructions
  2. The classloader — how Java finds and loads your classes (Spring uses this aggressively)
  3. The runtime memory model — heap, stack, metaspace (you'll hear these in interviews)
  4. Reflection — how Java code can inspect and call other Java code at runtime (this is how every annotation works)

1 · The compilation pipeline

Yesterday we said: Java compiles to bytecode. The JVM runs the bytecode. Let's get sharper than that.

Source.javaBytecode.classJVM Interpreterreads bytecodeJIT Compiler→ machine codejavacjavaruntimeinterpreted at first, then JIT-compiled to native code for hot paths
The four-stage pipeline. The JIT is what makes Java performant despite being interpreted.

Stage 1 · Source → Bytecode (compile time)

You run javac HelloWorld.java. The compiler produces HelloWorld.class — a binary file containing bytecode. Bytecode is like assembly for an imaginary CPU. It's portable: same on every OS.

Stage 2 · Bytecode → JVM (runtime)

You run java HelloWorld. The JVM starts, loads the class, and begins executing the bytecode. Initially it interprets bytecode — reads each instruction and emulates it. That is slow.

Stage 3 · JIT compilation (the secret sauce)

The JVM watches which methods run frequently ("hot" methods). For those, the JIT (Just-In-Time) compiler generates real native machine code on the fly and replaces the interpreted version. From then on, those methods run at near-C speed.

🧠 Why this is brilliant

Pure interpretation = portable but slow. Pure ahead-of-time compilation = fast but not portable. JIT gets both: portable bytecode that becomes fast machine code only for code paths that actually run a lot. This is why a long-running Java server eventually performs as well as C++ for hot endpoints.

2 · The Classloader — how Java finds your code

Here's a question almost no Java tutorial answers: when you write new UserService(), how does the JVM find the UserService class? It's not in memory yet. It lives in some .class file somewhere in your classpath or JAR.

The answer: the ClassLoader. ClassLoaders are special Java classes whose only job is finding bytecode and bringing it into memory.

Bootstrap ClassLoaderloads core JDK classes (java.lang, java.util...)Platform ClassLoaderJDK extension modulesApplication ClassLoaderyour code, your dependencies (Spring, Jackson...)
The three-level classloader hierarchy. Each level only sees its own scope.

When the JVM needs a class:

  1. It asks the App ClassLoader: "do you have UserService?"
  2. The App CL first asks its parent (Platform CL) — delegation
  3. Platform CL asks its parent (Bootstrap)
  4. If Bootstrap doesn't have it, the question bubbles back down until someone finds it
  5. If nobody finds it: ClassNotFoundException 💥
📌 Why you'll meet this in production

When you see NoClassDefFoundError, ClassNotFoundException, or "two versions of the same class on classpath" errors — that is the classloader. Spring Boot's "fat JAR" relies on a custom classloader that knows how to read classes from inside a nested JAR. This is the "magic" of java -jar app.jar.

3 · Memory model — heap, stack, metaspace

When the JVM runs, it splits its memory into regions. You don't need every detail. You need three:

Heapall objects live herenew User()new ArrayList()new String("hi")GC cleans thisshared by all threadsStack (per thread)method calls + local varsint x = 5;User u = ...; (ref)return valuefreed when method returnsprivate to one threadMetaspaceclass metadataUser.classmethod bytecodefield signatureswhere ClassLoader putsloaded classes
The three memory regions every Java developer must know.

Heap

Every object you create with new lives on the heap. Shared by all threads. The Garbage Collector sweeps unreferenced objects from here. When you hear "OutOfMemoryError: Java heap space" — this is the area.

Stack (one per thread)

Each thread has its own stack. Each method call creates a "frame" with that method's local variables. When the method returns, the frame is gone. Object references live on the stack, but the actual objects they point to live on the heap. This distinction matters in interviews.

Metaspace

When the ClassLoader loads User.class, it stores the class definition (methods, fields, bytecode) here — not on the heap. Before Java 8 this was called "PermGen." You only need to remember: classes themselves live in Metaspace.

🧠 Concrete example

Code: User u = new User();

Garbage collection — the one-paragraph version

The GC runs in the background, finds objects on the heap that no thread can still reach (no live reference points to them), and frees their memory. You never call free() in Java. This is huge — but the cost is occasional pauses called "GC pauses." For 99% of apps this is invisible. For ultra-low-latency systems (high-frequency trading), engineers pick GC algorithms carefully (G1, ZGC, Shenandoah). You don't need this depth now. Just know: memory is automatic, but not free.

4 · Reflection — Java looking at itself

Here is the JVM feature that powers every Java framework you'll touch.

Reflection is the ability of Java code, at runtime, to:

// Without reflection
User u = new User();
u.setName("Abhishek");

// With reflection — same effect
Class<?> cls = Class.forName("com.app.User");
Object u = cls.getDeclaredConstructor().newInstance();
Method m = cls.getMethod("setName", String.class);
m.invoke(u, "Abhishek");

The reflection version looks horrible. Why would anyone use it? Because frameworks need it.

🔑 The single most important realization in this chapter

When you write @Autowired UserRepository repo; — Spring is using reflection. It scans your classes, sees the annotation, finds a matching bean, and uses reflection to set the field for you. No magic. Just reflection + a registry of beans.

Same with @Entity, @Column, @Transactional, @RestController. Every annotation in Java is just metadata that some framework reads via reflection at runtime.

Once you internalize this, Spring stops being magical. It's just a well-organized program that reads your annotations and wires things together using reflection — exactly what you could write yourself if you had the patience.

One more piece — proxies (preview)

Reflection lets a framework read your code. But Spring sometimes needs to wrap your code — for example, to start a database transaction before your method runs and commit it after. How?

Spring uses dynamic proxies: at runtime, it generates a brand-new class that wraps yours, intercepting method calls. We won't go deep here — Week 3 (AOP) is dedicated to this. But know that proxies + reflection together are how:

The full picture

Source code.javaBytecode.class / .jarClassLoader→ MetaspaceJVM runninginterpret + JITAt runtime, Spring uses…reflectionto read your annotations+ proxies to wrap your methods
What happens from .java file to a Spring app handling requests.
Pause & reflect

Lock in today's learning

Answer in your own words. If you can't, that's the section you re-read.

  1. What does the JIT compiler do, and why does it make Java fast?
  2. What does a ClassLoader actually do? Why does Spring Boot need a special one?
  3. Where do objects live? Where do local variables live? Where do class definitions live?
  4. What is reflection in one sentence?
  5. How does @Autowired use reflection? Describe in 2–3 lines.
  6. What's the difference between NoClassDefFoundError at startup vs. at runtime? (hint: classloader scope)

End of Day 2. You now know more about how Java runs than 80% of mid-level Java developers.