Backend Notes NOV 25, 2025 • 04 MIN READ

HOW THE JVM HANDLES
CLASS LOADING
AND GARBAGE COLLECTION

Abstract technical background

As Java developers, we often focus on writing features, optimizing APIs, or fixing bugs. All while relying on the JVM to quietly do the heavy lifting underneath. Two of the most crucial mechanisms running behind the scenes are Class Loading and Garbage Collection (GC). These subsystems define how your code enters the runtime and how memory is managed once it’s running.

I’ll break them down in a way that aligns with everyday Java development.

The JVM Class Loading Mechanism

How your .class files come alive

Class loading is the process of taking your compiled bytecode (.class files) and making them available for execution inside the JVM. It’s handled by the ClassLoader subsystem, which works in three main phases:

Phase 1: Loading

The JVM locates the .class file and loads its binary data into memory.
Where do these classes come from?

  • check_circle Local file system
  • check_circle JARs inside your project
  • check_circle Network (yes, possible)
  • check_circle Runtime-generated bytecode (e.g., proxies)

Custom ClassLoaders can even load classes from databases, REST APIs, or encrypted sources. This is how frameworks like Spring dynamically generate proxies or enhanced classes.

Phase 2: Linking

This phase prepares the class for execution. It includes three sub-steps:

a. Verification

Checks whether the bytecode is valid and safe.
This prevents malformed or malicious bytecode from crashing the JVM.

b. Preparation

static int counter; // now exists in memory, default = 0

Allocates memory for:

  • check_circle Static variables
  • check_circle Default values (default int = 0, boolean = false, object references = null)

c. Resolution

Converts symbolic references (e.g., method names, field names) into real memory addresses.

Phase 3: Initialization

Now the static initializers and static blocks run:

SAMPLE.JAVA JAVA
static {
    System.out.println("Class loaded!");
}

This is the moment developers usually notice ClassLoader behavior when logs print unexpectedly early.

The ClassLoader Hierarchy

Here's the thing nobody tells you early in your Java journey: ClassLoaders are not equal. They’re hierarchical.
Just like a family, the JVM asks the parents first before doing anything itself.

  • check_circle Bootstrap ClassLoader. The ancestor. Loads core JDK classes.
  • check_circle Extension ClassLoader. Loads extension libraries.
  • check_circle Application ClassLoader. Loads your application’s classes and libraries.

This hierarchy prevents dangerous overrides. You cannot define your own java.lang.String.
Your ClassLoader will ask the parent, the parent asks its parent, and so on and the JDK classes win every time.

In Java, class loading follows a parent-delegation model.
This means: whenever a ClassLoader wants to load a class, it first asks its parent ClassLoader to try loading it. Only if the parent cannot load it, the child tries.

Because of that rule:

  • check_circle The Bootstrap ClassLoader (the top parent) loads all core JDK classes e.g., java.lang.String, java.util.List, java.io.File, etc.
  • check_circle Since the Bootstrap ClassLoader loads these classes first, your custom ClassLoader can never override them, because the parent will always answer with the official JDK version.

This prevents someone from writing a malicious version like:

STRING.JAVA JAVA
// NOT POSSIBLE
package java.lang;

public class String {
    // your own fake implementation
}

Even if you put a file like this in your app’s classpath, your ClassLoader will ask the parent first:

  • check_circle AppClassLoader → check parent (PlatformClassLoader)
  • check_circle PlatformClassLoader → check parent (Bootstrap)
  • check_circle Bootstrap → "I already have java.lang.String loaded."
  • check_circle Result → Your version is ignored.

This mechanism ensures security and stability, so core Java behavior cannot be modified or hijacked.

JVM Garbage Collection

How Java manages memory so you don’t manually free it.
The JVM heap is where objects live. Over time, unused objects pile up and this is where the Garbage Collector steps in.

JVM Memory Spaces

Young Generation

Objects are created here first.

  • check_circle Eden: where new objects are born
  • check_circle Survivor S0 & S1: where survivors migrate after GC cycles

Minor GCs happen here frequently and quickly.

Old Generation

Objects promoted from Young Gen live here. GC happens less often (Major GC).

Metaspace

Stores class metadata. No longer limited like the old PermGen.

How the JVM Decides Who Lives and Who Dies

The garbage collector’s rule is simple: “If no one can reach you, you no longer exist.”
GC determines reachability based on: threads, stack frames, static variables, active references.
If nothing points to an object, it becomes garbage.

The Minor GC

When Eden fills up:

  • check_circle The JVM pauses threads (briefly)
  • check_circle Copies surviving objects to Survivor spaces
  • check_circle Clears Eden
  • check_circle Promotes long-surviving objects to Old Gen

This is fast because copying memory is fast.
It’s why Java can handle thousands of TPS with proper GC tuning.

Modern Garbage Collectors

G1 GC (default in new Java)

  • check_circle Region-based
  • check_circle Predictable pause times
  • check_circle Used in most modern Spring Boot systems

ZGC

  • check_circle Ultra-low latency
  • check_circle Even with large heaps (GBs to TBs)

Shenandoah

  • check_circle Similar goals to ZGC
  • check_circle Concurrent compaction

Your choice of GC can literally determine whether your system survives high traffic.

How Does the JVM Decide When to Run GC?

GC is triggered when:

  • check_circle Eden space is full
  • check_circle Old Gen occupancy crosses a threshold
  • check_circle System memory pressure increases
  • check_circle GC algorithms’ internal heuristics decide it's optimal

You can monitor GC activity via:

  • check_circle Xlog:gc* (JDK 9+)
  • check_circle JDK Mission Control
  • check_circle VisualVM
  • check_circle Prometheus + Micrometer (Spring Boot)

Final Thoughts Mechanism

JVM Class Loading and Garbage Collection are invisible engines quiet, powerful, and often underappreciated. As Java developers, understanding them gives us superpowers.
It helps us debug smarter, design cleaner systems, and build applications that scale gracefully.
If you're building high-performance services, diving deeper into GC tuning, classloader isolation, and bytecode generation is worth every minute.

READ BEYOND THE VOID

Technical blueprint background
Backend Notes

Learning Tree Concept in Java

Understanding Tree concept in Java...

Read Entry
Abstract digital network
Backend Notes

Difference between sleep() and wait() in threads

What difference between sleep() and wait() in threads...

Read Entry
Circuit board macro
Backend Notes

Volatile vs Synchronized vs Lock in Java

What difference between volatile, synchronized and lock in Java...

Read Entry