Core Java Platform Mechanics

Part 2 of 5 in Mastering Modern Java Jvm

Java has accumulated a remarkable amount of language-level and JVM-internal machinery over its two-decade history. The result is a platform where modern APIs for type-safe hierarchies, concurrency, and memory management coexist with decades of legacy. Understanding what these tools actually do — and how the JVM makes them work under the hood — is what separates competent Java code from robust application development.

This post walks through three areas: language enhancements (sealed classes, records, pattern matching), concurrency primitives (virtual threads and the thread-per-request pattern), and low-level execution mechanics (the classloader delegation chain, generational GC, and resource cleanup).

Sealed Classes, Records, and Pattern Matching

Sealed classes are Java’s answer to algebraic data types. They declare which types can extend or implement them, closing the hierarchy at compile time. Combined with records (immutable value carriers with auto-generated equality methods) and pattern matching in switch and instanceof, they eliminate entire categories of runtime errors.

The Shape hierarchy below demonstrates this: a sealed interface permits exactly three subclasses, one of which (Rectangle) is itself sealed to further restrict the type space. Pattern matching in switch uses type narrowing so you can access members without casting, and the compiler guarantees exhaustiveness — if you add a new permitted subclass, every switch on Shape becomes a compile error until updated.

sealed interface Shape permits Circle, Rectangle, Triangle {
    double area();
}

record Circle(double radius) implements Shape {
    public Circle { if (radius <= 0) throw new IllegalArgumentException(); }
    @Override public double area() { return Math.PI * radius * radius; }
}

sealed class Rectangle implements Shape permits Square, RotatedRectangle {
    protected final double width;
    protected final double height;
    ...
}

The output confirms several things at once. The describe() method uses pattern matching with a when guard to distinguish squares from rectangles inside the same Rectangle case — this narrowing works because the switch on a sealed interface only sees permitted subclasses, so every branch is reachable and exhaustively covered without a default. Records like Point provide structural equality out of the box (p1.equals(p3) is true when both have the same data), while records also expose their components via simple accessor methods — circle.radius() instead of getRadius() — because they are transparent carriers, not classes hiding state.

Virtual Threads and Thread-Per-Request Concurrency

Platform threads map one-to-one to OS kernel threads: each carries a ~1 MB stack, and creating thousands of them exhausts memory or overwhelms the scheduler. Java 21’s virtual threads solve this by having the JVM schedule lightweight VM-level threads onto a small pool of carrier (platform) threads. A Thread.sleep() inside a virtual thread unpark it — the platform carrier moves on to another task.

The difference in scaling is stark:

// 5 platform threads, each doing ~50ms+ of sleep work
Thread.ofPlatform().start(() -> { ... })

// vs.

// 10,000 virtual threads doing the exact same work
Thread.startVirtualThread(() -> { ... })

The output shows platform threads completing 5 concurrent tasks in ~73 ms, while 10,000 virtual threads (all sleeping concurrently for ~800 ms total) also complete — with no memory pressure. The thread-per-request executor (Executors.newVirtualThreadPerTaskExecutor()) handles 1,000 concurrent I/O-bound tasks in ~97 ms wall time.

Notice how virtual threads carry no name by default and report isVirtual() == true — this is useful for monitoring, because most JVM tooling can now distinguish between heavyweight platform threads (which warrant investigation) and lightweight virtual threads (which are the expected workload). The thread-per-request pattern replaces the old ThreadPoolExecutor(core=N) sizing dance: instead of guessing the optimal pool size based on CPU cores or I/O ratio, you create a thread per request and let the JVM’s scheduler manage the mapping to carriers.

ClassLoader Hierarchy, Generational GC, and Resource Cleanup

Beyond language features, Java’s execution model operates across several layered abstractions. The classloader hierarchy follows strict delegation: every application-loaded class passes through AppClassLoader → PlatformClassLoader → <bootstrap>. Bootstrap classes (like String) are loaded by the null bootstrap classloader, which lives in native code and isn’t visible to Java reflection.

The GC generational model works by partitioning the heap. New objects start in Eden; surviving young-generation collections get promoted to old generation. Running 50 allocation cycles of 100 objects each created 5,000 instances total — only 50 survived promotion to the old generation because the rest were unreachable after their scope ended.

Class metadata lives in Metaspace (native memory since Java 8), not the heap — reflection can inspect it but no GC applies. Finally, finalize() is deprecated precisely because its timing is unpredictable; the Cleaner API (java.lang.ref.Cleaner.create().register(obj, action)) gives you explicit lifecycle hooks that are far more reliable for resource management.

Takeaway

Modern Java’s value isn’t any single feature — it’s the way they interlock. Sealed classes + pattern matching eliminate unreachable-code bugs at compile time; virtual threads turn thread-per-request concurrency from a memory-management exercise into a simple Executors.newVirtualThreadPerTaskExecutor() call; and understanding the classloader delegation chain and generational GC model helps you reason about plugin architectures, leak sources, and performance characteristics without guessing. The platform’s depth is what makes it viable for large-scale applications, and writing code that respects its mechanics pays dividends in both correctness and maintainability.