Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
  • MockMvc, WebTestClient & Testcontainers in Depth — Interview Questions
  • REST Principles, Status Codes & Resource Design — Interview Questions
  • OpenAPI, Validation Errors, API Versioning & GraphQL — Interview Questions

Build, DevOps & Cloud

  • Maven & Gradle at Scale — Interview Questions
  • Git, CI/CD Pipelines & Release Safety — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
  • MockMvc, WebTestClient & Testcontainers in Depth — Interview Questions
  • REST Principles, Status Codes & Resource Design — Interview Questions
  • OpenAPI, Validation Errors, API Versioning & GraphQL — Interview Questions

Build, DevOps & Cloud

  • Maven & Gradle at Scale — Interview Questions
  • Git, CI/CD Pipelines & Release Safety — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)JVM Internals & Performance
✓ FreeAdvanced· 12 min read

Class Loading, Bytecode & Object Layout — Interview Questions

JVM internals for senior engineers — class loader types and the parent-delegation model, how delegation prevents class spoofing, when and how it is deliberately broken, the loading/linking/initialisation pipeline, defineClass and custom class loaders, classes with the same name in one JVM, where class metadata lives, bytecode verification, bytecode inspection tools, invoke* instructions and invokedynamic, object header layout and compressed oops.

Published September 25, 2026


How to use this lesson

These questions are asked to engineers who debug ClassCastException: X cannot be cast to X, application-server classloader leaks, or container memory sizing. Tie every answer to a production symptom you could diagnose with it.

Q1. What types of class loaders does Java have, and how does the delegation model work?

Short answer: Since Java 9, there are three built-in loaders:

  1. Bootstrap: native code. It loads the core modules (java.base and others) from the runtime image. In Java code it appears as null.
  2. Platform: replaced the old Extension loader. It loads the remaining Java SE and JDK modules.
  3. Application (system): loads classes from the classpath or module path, meaning your code and libraries.

Frameworks add their own (web-application loaders in Tomcat, Spring Boot's nested-JAR loader, OSGi bundles, plugin loaders).

Parent delegation: loadClass(name) works in three steps:

  1. It checks whether the class is already loaded (findLoadedClass).
  2. If not, it asks the parent first.
  3. Only if the parent can't find it does it call its own findClass.
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try { c = getParent() != null ? getParent().loadClass(name) : findBootstrapClassOrNull(name); }
            catch (ClassNotFoundException ignored) { }
            if (c == null) c = findClass(name);          // our own lookup, last
        }
        if (resolve) resolveClass(c);
        return c;
    }
}

Learn it in depth → JVM Memory Areas

Q2. How does parent delegation prevent security problems like class spoofing?

Short answer: Because the parents are asked first, a request for java.lang.String always resolves to the bootstrap loader's real class. A malicious java/lang/String.class on the classpath is never used. On top of that:

  • the JVM forbids defining classes in java.* packages from non-bootstrap loaders (SecurityException: Prohibited package name);
  • JPMS prevents a package from being split across modules;
  • package sealing (in JAR manifests) stops foreign classes from being injected into your packages.

The result is that core types (String, Class, Object) and their invariants can't be replaced.

Q3. Can the delegation model be broken? When is that useful?

Short answer: Yes. Override loadClass to do child-first (parent-last) loading, or use the thread context class loader. Legitimate uses:

  • Servlet containers: each web app's loader looks in WEB-INF/lib first, so different applications can use different library versions. Java EE and Jakarta API classes are still delegated to the parent.
  • Plugin systems and OSGi: each plugin or bundle has its own loader, and a graph of loaders (not a tree) resolves packages across bundles.
  • Hot reload and redeploys: throw away a loader, and load new classes in a fresh one (DevTools' restart loader, JRebel-style tools).
  • SPI loading from core code: JDBC DriverManager, JNDI and ServiceLoader are loaded by parent loaders, but must find application-level implementations. They use the thread context class loader (Thread.currentThread().getContextClassLoader()), which is an inversion of delegation.

Key points to cover:

  • The price is type-identity problems (the same class name loaded twice → ClassCastException, LinkageError) and classloader leaks on redeploy.

Q4. What happens internally when a class is loaded?

Short answer: Per the JVM specification, there are three phases:

  1. Loading: find the binary representation (a class file or runtime image), parse it, create the internal class structure in Metaspace, and create a java.lang.Class mirror on the heap.
  2. Linking:
    • Verification: bytecode safety checks.
    • Preparation: allocate static fields, and set them to default values.
    • Resolution: turn symbolic references (class, method and field names in the constant pool) into direct references. HotSpot does this lazily, on first use.
  3. Initialisation: run <clinit> (static initialisers, and static field assignments in textual order), exactly once, thread-safely, and triggered by the first active use: new, a static method call, a static field access (except compile-time constants), reflection, or initialising a subclass.

Key points to cover:

  • This laziness is why the initialisation-on-demand holder singleton is thread-safe and lazy.
  • It's also why a failing static initialiser gives ExceptionInInitializerError once, and NoClassDefFoundError on every later attempt.

Q5. What is defineClass() for in a custom class loader?

Short answer: defineClass(name, bytes, off, len[, protectionDomain]) is the protected final method that turns a byte array into a Class object owned by this loader. The JVM parses and verifies the bytes, and records this loader as the defining loader. A custom loader typically overrides findClass: it fetches the bytes (from a database, the network, an encrypted JAR, or generated code), then calls defineClass.

public class PluginClassLoader extends ClassLoader {
    private final Path pluginDir;
    public PluginClassLoader(Path pluginDir, ClassLoader parent) { super("plugin", parent); this.pluginDir = pluginDir; }

    @Override protected Class<?> findClass(String name) throws ClassNotFoundException {
        Path file = pluginDir.resolve(name.replace('.', '/') + ".class");
        try {
            byte[] bytes = Files.readAllBytes(file);
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
}

Key points to cover:

  • Override findClass, not loadClass, to keep parent delegation intact (unless you deliberately want child-first loading).
  • Register the loader as parallel-capable (ClassLoader.registerAsParallelCapable()) for concurrency.
  • For generated classes within an existing package, modern code uses MethodHandles.Lookup.defineClass or defineHiddenClass (Java 15), instead of custom loaders.
  • URLClassLoader covers the common "load from JARs" case.

Q6. Can two classes with the same name exist in one JVM?

Short answer: Yes, if different class loaders define them. At runtime, a class's identity is the pair (fully qualified name, defining class loader). So com.acme.Plugin loaded by loader A and loader B are different types. Casting an instance of one to the other throws the famous ClassCastException: com.acme.Plugin cannot be cast to com.acme.Plugin. The loader constraints can also produce LinkageError: loader constraint violation.

This happens with web applications sharing a container, plugin systems, hot reload (DevTools: the old and new restart loaders), and duplicate JARs in different loaders. The fix is to put shared API types in a common parent loader.

Q7. What's stored in class metadata, and where does it live in modern JVMs?

Short answer: Class metadata includes:

  • the runtime constant pool;
  • field and method descriptors;
  • method bytecode;
  • vtables and itables;
  • annotations;
  • method counters and profiling data;
  • links to the superclass, interfaces and defining loader.

Since Java 8, it lives in Metaspace: native memory, off the Java heap, allocated per class loader, and freed when the loader is collected. It replaced PermGen. With compressed class pointers, class structures sit in the Compressed Class Space (1 GB by default).

Key points to cover:

  • Static fields live in the heap, in the Class mirror object.
  • Interned strings are on the heap (since Java 7).
  • JIT-compiled code lives in the code cache.

Tuning: -XX:MaxMetaspaceSize (a leak guard), MetaspaceSize (the first GC threshold). Diagnose with jcmd <pid> VM.metaspace.

Q8. What is bytecode verification, and what security does it enforce?

Short answer: Before a class is linked, the verifier checks that its bytecode is structurally and type-safe, so malformed or malicious class files can't corrupt the JVM. It checks that:

  • the operand stack doesn't overflow or underflow;
  • every instruction gets operands of the correct types (you can't treat an int as a reference, which would forge pointers);
  • branches target valid instruction boundaries;
  • locals are initialised before use;
  • access rules hold (no calls to private methods of other classes);
  • objects are initialised (constructor called) before use;
  • final rules are respected.

Since Java 7, class files carry StackMapTable frames, so the type-checking verifier runs in a single fast pass.

Common trap: turning verification off with -Xverify:none or -noverify for "faster startup". It's deprecated since Java 13, and unsafe. Use CDS or AOT caches for startup instead.

Q9. Which tools can you use to inspect bytecode?

Short answer:

  • javap -c -v -p MyClass: disassembles bytecode, and shows the constant pool, stack map frames and flags.
  • IDE viewers: IntelliJ's View → Show Bytecode, and the Eclipse Bytecode Outline.
  • jclasslib Bytecode Viewer (a GUI). The ASM and ByteBuddy libraries, for programmatic reading and writing. The Class-File API (java.lang.classfile), finalised in Java 24, for parsing and generating class files.
  • Decompilers, to go back to source: CFR, Fernflower (in IntelliJ), Procyon, Vineflower.
  • For JIT-level inspection: -XX:+PrintCompilation, JITWatch, and -XX:+PrintAssembly (needs the hsdis plugin).

Q10. How does the JVM resolve method calls in bytecode (invokestatic, invokevirtual, and so on)?

Short answer:

  • invokestatic: static methods. The target is known at link time, so there's no receiver and no dispatch.
  • invokespecial: constructors (<init>), super.method() calls, and (before Java 11) private methods. Non-virtual: the exact method is fixed.
  • invokevirtual: instance methods on classes. Dynamic dispatch through the receiver class's vtable (a fixed slot index per method).
  • invokeinterface: interface methods. Dispatch through itables, which is slightly costlier, because the slot varies per implementing class.
  • invokedynamic: call sites linked at runtime by a bootstrap method, which returns a CallSite/MethodHandle. Used for lambdas (LambdaMetafactory), string concatenation (Java 9+), records' toString/equals/hashCode, and pattern-matching switch.

Key points to cover:

  • Nestmates (Java 11) let nested classes call each other's private members without synthetic accessor methods. Private methods are now invoked with invokevirtual/invokeinterface, but can't be overridden.
  • In practice, the JIT turns most virtual calls into direct or inlined calls using type profiles (monomorphic and bimorphic inline caches). Truly megamorphic sites keep the vtable or itable dispatch.

Q11. How would you implement a custom class loader?

Short answer:

  1. Extend ClassLoader (or URLClassLoader), passing an explicit parent.
  2. Override findClass(String): locate the bytes, then call defineClass.
  3. Override findResource/findResources if the loader also serves resources.
  4. Declare it parallel-capable.
  5. Load through it with Class.forName(name, true, loader), and only cast to interfaces loaded by a common parent.
  6. Close it (URLClassLoader.close()) and drop all references, so its classes can be unloaded.

Use cases: plugins, per-tenant isolation of script engines, loading encrypted or generated code, hot reloading. Remember that it's a modularity boundary, not a security boundary.

Q12. What is the object header, and how is an object laid out in memory?

Short answer: In HotSpot on 64-bit JVMs, every object starts with a header:

  • The mark word (8 bytes): the identity hash code (once computed), the GC age bits, lock state bits (lightweight or inflated monitor pointer), and GC forwarding information.
  • The class pointer (klass): 4 bytes with compressed class pointers (the default), otherwise 8.
  • For arrays, a 4-byte length.

Then come the fields, reordered by the JVM for alignment (longs and doubles, then ints, then shorts and chars, then bytes and booleans, then references), with the whole object padded to 8 bytes. So new Object() takes 16 bytes, and a Boolean wrapper takes 16 bytes to hold one bit.

Key points to cover:

  • Compact object headers (JEP 450, experimental in Java 24; a product option in Java 25, JEP 519, with -XX:+UseCompactObjectHeaders) merge the class pointer into the mark word. That gives 8-byte headers, and typically 10–20% less heap for object-heavy applications.
  • Tool: JOL (Java Object Layout) prints real layouts: ClassLayout.parseClass(Order.class).toPrintable().

Q13. What are compressed OOPs?

Short answer: Compressed ordinary object pointers: on 64-bit JVMs, object references are stored as 32-bit offsets, scaled by the object alignment (8 bytes), and shifted when they're decoded. That lets 32-bit references address up to about 32 GB of heap, while saving memory and cache footprint on every reference field and array slot. It's on by default when -Xmx is below about 32 GB. HotSpot uses zero-based compressed oops when the heap fits below 4 GB or 32 GB in virtual address space, which avoids even the base addition.

Common trap: raising -Xmx from 31 GB to 33 GB disables compressed oops. All references double to 8 bytes, so you can end up with less effective capacity than before. Either stay below about 31 GB, or go well above (48 GB or more). You can also raise -XX:ObjectAlignmentInBytes=16 to extend the compressed range to 64 GB, at some padding cost.

Follow-up questions this topic invites — and their answers

Q: What's the difference between ClassNotFoundException and NoClassDefFoundError? A: ClassNotFoundException is a checked exception from explicit loading (Class.forName, loadClass) when the class can't be found. NoClassDefFoundError is an error when a class that was present at compile time can't be loaded or linked at runtime: it's missing from the classpath, or its static initialiser failed earlier.

Q: When are classes unloaded? A: Only when their defining class loader becomes unreachable, together with all its classes and instances. Then the GC can reclaim them, and free their Metaspace. Classes loaded by the bootstrap, platform and application loaders are never unloaded.

Q: What is the thread context class loader, and why does it cause leaks? A: It's a per-thread loader that frameworks use to find application classes from library code. Pooled threads that keep a reference to an old web application's loader (through the TCCL, or ThreadLocals) keep the entire old application in memory after a redeploy, which is a classic Metaspace leak.

Q: What are hidden classes (Java 15)? A: Classes defined with Lookup.defineHiddenClass. They can't be found by name or linked by other classes, can be unloaded independently, and are meant for framework-generated code (lambda proxies, dynamic proxies, Groovy or JRuby runtimes). They replaced the internal Unsafe.defineAnonymousClass.

Previous

Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

Next

JIT Compilation & Runtime Optimisations — Interview Questions

AI Tutor

Lesson: Class Loading, Bytecode & Object Layout — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.