Week 5 - Class Loading and Bytecode¶
Conceptual Core¶
Java compiles to bytecode, an idealized stack-machine ISA, which the JVM loads, verifies, links, and (eventually) JIT-compiles to native. A class is not just source-level - it's a runtime resource with an identity: the <defining classloader, FQN> pair. Two classes with the same fully-qualified name loaded by two different classloaders are different classes and not assignment-compatible. Internalize this and most production classloader bugs become obvious.
Class loading is lazy, hierarchical (parent-delegation), and the source of a whole genre of production bugs: ClassNotFoundException, NoClassDefFoundError, LinkageError, and metaspace classloader leaks.
The trap
Assuming "the class is loaded" is one event. It's three phases: load → link → initialize. The init phase runs your static {} lazily, the first time the class is actively used. Reading a static final constant does NOT initialize it; calling a static method does. JLS §12.4 enumerates triggers - read them before debugging your next "static block ran twice" bug.
Mechanical Detail¶
- Class-file format (JVMS Ch. 4): magic
CAFEBABE, constant pool, access flags, fields, methods, attributes. Read it:javap -v -p YourClass.class. - Bytecode is a stack machine - ~200 opcodes, you'll meet ~30 daily.
invokedynamic(7+) is how lambdas, switch-on-strings, and pattern matching are implemented under the hood. - Linking sub-phases: verify (bytecode well-formed), prepare (static field storage), resolve (symbolic refs → direct). Then initialize (
<clinit>). Init failure throwsExceptionInInitializerErrorand bricks the class for the rest of its loader's life. - Loader hierarchy (9+): bootstrap → platform → app → custom.
Thread.currentThread().getContextClassLoader()exists becauseServiceLoader/JDBC/JNDI need to look down the hierarchy, which parent-delegation forbids; the TCCL is the workaround. - Custom classloaders power plugin systems, hot reload, multi-tenant isolation, and bytecode-rewriting frameworks (Hibernate, Mockito).
- Class-File API (JEP 484, stable in 24+) -
java.lang.classfilereads/writes class files declaratively. Replaces ASM for new code.
Lab¶
Take a 10-line Java method. Compile it. Read the javap -v -p output line by line. Then build a class with the same bytecode using the Class-File API, load it via a custom ClassLoader, invoke via reflection, confirm output matches.
Idiomatic Drill¶
Find a real classloader leak (search "metaspace leak Tomcat"). The canonical pattern: a ThreadLocal<T> whose T is loaded by a webapp classloader, kept alive by a long-lived worker thread that survives redeploy. Fix: ThreadLocal.remove() in a finally - or ScopedValue (final in 25).
Production Hardening Slice¶
Add to hardening/: