The static block that printed “too late”
Riya's team wrote a Config class whose static block prints “Config loaded” and reads settings. She expected the message as soon as the app started. Instead it appeared halfway through the logs, right before the first payment. And on the test server, a missing JAR made the app fail with NoClassDefFoundError, an error she had never seen in college.
Sir, why did “Config loaded” print so late? The app had been running for a minute.
Because the JVM loads classes lazily. A class's static block runs on its first active use: creating an object, calling a static method, or reading a non-constant static field. Nobody touched Config until the first payment.
And the NoClassDefFoundError on the test server?
The class was there when you compiled, but missing (or broken) when the JVM tried to load it. Let's follow a class from the .class file to “ready”, and both mysteries will make sense.
What is class loading?
Class loading is the process by which the JVM makes a class available at run time. It has three phases. Loading: a class loader finds the binary representation (the .class bytes) and creates the class in the method area. Linking: verification checks the bytecode is valid and safe, preparation allocates static fields and sets them to default values, and resolution replaces symbolic references with direct ones (this may happen lazily). Initialization: the class's static initializer (<clinit>: static field initializers and static blocks, in source order) runs exactly once, on the class's first active use, after its superclass has been initialized.
Before a new employee starts work: HR finds their file (loading), checks the documents (verify), gives them an empty desk (prepare), adds their phone number to the directory (resolve), and on their first real day they set up their desk (initialise), only once.
Words you'll meet in this lesson
<clinit>: the static field initializers and static { } blocks of a class.new, static method call, non-constant static field access…static final primitive or String with a constant value. javac copies it into the user's code.Class loading facts at a glance
| Phases | Load → Link (verify, prepare, resolve) → Initialise |
|---|---|
| Initialised when | new, a static method call, reading or writing a non-constant static field, Class.forName(name), a subclass being initialised, or being the main class |
| NOT initialised by | Reading a compile-time constant, creating an array of the type (new Config[3]), a class literal (Config.class), accessing a parent's static field through a child |
| Built-in loaders | Bootstrap (core JDK, shown as null) → Platform (Java 9+, other JDK modules; was “Extension” in Java 8) → Application/System (your class path) |
| Errors | ClassNotFoundException (checked, from Class.forName/loadClass); NoClassDefFoundError (class present at compile time, missing or failed at run time); ExceptionInInitializerError (static initializer threw) |
| Watch it | java -verbose:class Main prints every class as it is loaded |
Exam tip: a class is loaded and initialised at different moments. new Config[3] may load Config but does not initialise it, so its static block doesn't run. Initialisation happens at most once per class (per class loader), and a superclass is always initialised before its subclass.
Three class loaders, one delegation rule
Bootstrap
Built into the JVM (native code). Loads the core classes of java.base: String, Object, ArrayList. Java shows it as null.
Platform
Java 9+ (replaced the Java 8 “extension” loader). Loads other JDK modules such as java.sql and java.xml.
Application
Also called the system class loader. Loads your classes and libraries from the class path (-cp) or module path.
Watching loading vs initialisation
public class Main {
static class AppConfig {
static final int MAX_USERS = 100; // compile-time constant
static String env = load();
static {
System.out.println(" AppConfig static block, env = " + env);
}
static String load() {
System.out.println(" AppConfig.load() runs");
return "prod";
}
}
static {
System.out.println("Main initialised");
}
public static void main(String[] args) {
System.out.println("main starts");
System.out.println("MAX_USERS = " + AppConfig.MAX_USERS);
AppConfig[] slots = new AppConfig[3];
System.out.println("array of " + slots.length + " created");
Class<?> c = AppConfig.class;
System.out.println("class literal: " + c.getSimpleName());
System.out.println("env = " + AppConfig.env);
System.out.println("env again = " + AppConfig.env);
}
}The same program with -verbose:class, keeping only our two classes (about 530 JDK classes, mostly from the CDS archive, and a few generated helper classes are left out; the path is shortened):
java -verbose:class MainNotice: Main$AppConfig is loaded when the array is created, but its static block runs only when env is read.
ClassNotFoundException vs NoClassDefFoundError
ClassNotFoundException | NoClassDefFoundError | |
|---|---|---|
| Type | Checked exception | Error (a LinkageError) |
| Thrown when | You load a class by name (Class.forName, loadClass) and no loader finds it | Code compiled against a class runs, but the class is missing at run time, or its initialisation failed earlier |
| Typical cause | Typo in a class name, driver JAR not on the class path | Missing JAR at deploy time; a static initializer threw (“Could not initialize class X”) |
| Example message | java.lang.ClassNotFoundException: com.shop.PaymentGateway | java.lang.NoClassDefFoundError: Helper with Caused by: java.lang.ClassNotFoundException: Helper |
| Handle it? | Yes, catch it and report or fall back | Fix the deployment or the initializer; don't catch and continue |
Static initializers are fragile: if one throws, the JVM wraps the exception in ExceptionInInitializerError and marks the class as failed. Every later use throws NoClassDefFoundError: Could not initialize class … until the JVM restarts. Keep static blocks small and never let them depend on things that can fail, like a network call.
From .class file to a ready class
Loading: find the bytes, with delegation
The application loader first asks its parent (platform), which asks the bootstrap loader. Neither hasnew TaxConfig(); // first time TaxConfig is neededTaxConfig, so the application loader readsTaxConfig.classfrom the class path and defines the class. Delegation stops anyone from replacing core classes such asjava.lang.Stringwith their own.Verify
The verifier checks the bytecode: correct types on the operand stack, no jumps outside the method, valid references to fields and methods.
This is why a hand-edited or corrupted.classfile fails with aVerifyErrorinstead of crashing the JVM. JDK classes from the CDS archive are already verified, which helps start-up.Prepare
Memory for static fields is allocated and set to defaults: 0, 0.0, false, null. Compile-time constants (static String state = loadState(); // state = null for now static int gstPercent; // 0static final int MAX = 100) already get their real value here. No Java code runs in this phase.Resolve
Symbolic names in the constant pool (“method
HotSpot resolves lazily: each reference is resolved the first time the instruction using it runs. That's also when a missing class referenced from your code surfaces asTaxConfig.loadState”) are turned into direct references.NoClassDefFoundError.Initialise: run <clinit> once
The superclass is initialised first. Then the static field initializers and static blocks run in source order, in one compiler-generated methodstatic { gstPercent = state.equals("MH") ? 18 : 12; }<clinit>. The JVM takes a lock while doing this, so even with many threads it runs exactly once.
One class, every phase: both views
Left: the code and the console. Right: the class loaders and the class's progress through the phases.
AppConfig 6 steps- Load: creating an
AppConfig[]makes the JVM load the class;-verbose:classprints a line. - Link: the class is verified and prepared. The program continues and prints the array line.
- Not yet: arrays and class literals don't count as active use, so the static block hasn't run.
- Initialise: reading the non-constant static field
envruns the static initializer:load(), then the static block. - Once only: the class is ready; later reads just return the value.
- Failure: if the initializer throws, you get
ExceptionInInitializerErroronce, thenNoClassDefFoundErroron every later use.
- The application loader delegates to platform, which delegates to bootstrap. Neither knows
Main$AppConfig, so the application loader reads it from the class path. - Verification passes. Preparation creates the static fields: the constant gets 100 straight away;
envgets null. - The class is loaded and linked but not initialised. Its static block hasn't run.
- The
getstaticforenvis the first active use. References are resolved as they are used, and<clinit>runs:load()then the static block. envnow holds "prod". The class is marked initialised; this never happens again for this class loader.- If
<clinit>throws, the class is marked erroneous. The first caller seesExceptionInInitializerError; every later caller seesNoClassDefFoundError.
Class loading in real systems
You rarely write a class loader yourself, but every framework you use depends on these rules.
Watch it with an example: a shop loading its UPI payment plug-in
The checkout loads payment providers by name from a plug-in JAR. Watch delegation find the class, linking prepare it, and the static block run only when the first payment arrives.
- Start-up: the shop starts with both JARs on the class path, but
UpiProviderisn't loaded: no one has needed it yet. - First payment: the checkout loads the provider by name. The application loader first asks platform, which asks bootstrap. Neither knows a class called
pay.UpiProvider. - Found: the application loader reads the class from
upi.jarand defines it. - Linked: the bytecode is verified and the static field
feePaiseis prepared with its default, 0. - Initialised:
Class.forNamealso initialises the class, so the static initializer runs once: the fee becomes 200 paise and “UPI ready” is printed. - Next payments: the class is already loaded and initialised, so nothing runs again. A provider whose JAR is missing fails with
ClassNotFoundException, which the checkout can catch and report.
1. Application servers
A server such as Tomcat gives each deployed web app its own class loader, so two apps can use different versions of the same library. Undeploying an app should make its loader (and all its classes) unreachable; if something still references it, metaspace leaks.
2. Database drivers and plug-ins
Old JDBC code loaded drivers with Class.forName("…Driver"), whose static block registered the driver. Modern drivers are found automatically through ServiceLoader, which also loads classes by name from JARs.
3. Lazy, thread-safe singletons
The holder-class idiom puts a singleton in a static field of a nested class. Because a class is initialised once, under a JVM lock, on first use, you get lazy creation and thread safety without synchronized.
4. Faster start-up
Loading and verifying thousands of classes takes time, which matters for serverless functions and command-line tools. CDS archives (on by default for JDK classes) and application CDS (-XX:SharedArchiveFile) store pre-parsed classes to cut it down.
5. Dependency problems at deploy time
A library compiled against version 2 of a JAR but deployed with version 1 fails with NoClassDefFoundError or NoSuchMethodError. Build tools (Maven, Gradle) can show the dependency tree to find the conflict.
Examples are simplified. Frameworks such as Spring and Hibernate also generate classes at run time (proxies), which are loaded by the same rules.
Who loads what, and when
public class Main {
static class Account {
static int accounts = 0;
static {
System.out.println("Account class initialised");
}
Account() { accounts++; }
}
static class Savings extends Account {
static double rate = 3.5;
static {
System.out.println("Savings class initialised");
}
}
public static void main(String[] args) {
System.out.println("main starts");
Savings s = new Savings();
Savings t = new Savings();
System.out.println(Account.accounts + " accounts at " + Savings.rate + "%");
System.out.println("Savings loaded by: " + Savings.class.getClassLoader().getName());
System.out.println("String loaded by: " + String.class.getClassLoader());
}
}new Savings()First active use of Savings. Its superclass Account is initialised first.Savings t = new Savings();No static block runs the second time; only constructors.getClassLoader().getName()Your classes come from the application loader, named "app".String.class.getClassLoader()nullmeans the bootstrap loader, which is part of the JVM itself.
Parent class first, then child, then objects
Recorded from the real JVM. The static entries appear with default values as soon as the classes are prepared; watch the static initializer frames fill them in, once.
Worked examples: real problems, solved and traced in memory
Three business programs where the order of class initialisation matters. Watch the static entries: a class's static fields appear with default values when it is prepared, get their real values when its static initializer runs, and that happens only once.
A config class that loads itself on first use
Problem statement
A billing service keeps tax settings in a TaxConfig class whose static fields are filled by a static initializer (here the state code is hard-coded; in real life it would come from a file). Show exactly when the configuration is loaded, and compute the GST on a Rs 2,000 bill.
state = MH, GST 18% for MH, bill = Rs 2000
Billing starts TaxConfig loaded for MH Tax: Rs 360
Approach
- Put the settings in static fields and compute them in a static block, so they are prepared once for the whole program.
- The static initializer runs on the first active use of the class, here
new TaxConfig(), not when the program starts. - After that, every object and method reads the same already-initialised static fields.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Lazy singleton with a holder class
Problem statement
Creating a payment-gateway client is expensive, so the app wants exactly one, created only when the first payment happens. Use the initialization-on-demand holder idiom and show that two calls return the same object.
getInstance() called twice
App up, no client yet Same client? true, created 1
Approach
- Put the single instance in a static field of a private nested class
Holder. getInstance()readsHolder.INSTANCE. The first read is Holder's first active use, so the JVM initialises Holder then, creating the client.- The JVM guarantees a class is initialised only once, even with many threads, so there is no need for
synchronized.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Constants are not fields: what triggers initialisation
Problem statement
Invoices are numbered from 1001 using a static counter, and GST is a constant 18%. Print the GST rate first, then create two invoices. Trace when class Invoice is initialised.
amounts: Rs 500, Rs 1200
GST is 18% 1001: Rs 590 1002: Rs 1416
Approach
static final int GST_PERCENT = 18is a compile-time constant: javac copies 18 into the code that uses it, so reading it does not initialise Invoice.static int nextNumber = 1001is an ordinary static field. The firstnew Invoicetriggers initialisation and sets it.- Each constructor takes the next number with
nextNumber++: one counter shared by all invoices.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 8.4 in one screen
- Phases: load → link (verify, prepare, resolve) → initialise. Classes are loaded lazily, when first needed.
- Prepare = static fields get defaults; initialise = static initializers and blocks run, in source order, once.
- Initialised on first active use:
new, static method, non-constant static field,Class.forName, subclass init, main class. - Not on: compile-time constants, arrays of the type, class literals, parent field via child.
- Loaders: bootstrap (null) → platform (Java 9+) → application; parent delegation.
ClassNotFoundException= loading by name failed;NoClassDefFoundError= was there at compile time, missing or broken now.- See it:
java -verbose:class; a failed static block givesExceptionInInitializerErrorthen NCDFE.
Interview and viva questions
Explain the class loading process in Java.
A class loader finds the class's bytes and defines the class (loading). The JVM then links it: verifies the bytecode, prepares static fields with default values, and resolves symbolic references (often lazily). Finally it initialises the class by running its static field initializers and static blocks, once, on the first active use, after initialising its superclass.
What are the built-in class loaders and what is delegation?
Bootstrap (native, loads core java.base classes), platform (Java 9+, other JDK modules; the extension loader in Java 8) and application or system (the class path). Each loader first delegates to its parent; only if the parent can't find the class does it load it itself. This keeps core classes from being replaced and avoids loading the same class twice.
ClassNotFoundException vs NoClassDefFoundError?
ClassNotFoundException is a checked exception thrown when code explicitly loads a class by name and it isn't found. NoClassDefFoundError is an error thrown when a class that was available at compile time can't be found or initialised at run time, for example a missing JAR or a static initializer that failed earlier.
When exactly does a static block run?
Once, when the class is initialised: on its first active use, such as creating an instance, calling a static method, accessing a non-constant static field, calling Class.forName, initialising a subclass, or when it is the main class. Not when merely declaring a variable of the type, creating an array of it or reading a compile-time constant.
Can two classes with the same name exist in one JVM?
Yes, if different class loaders load them. A class's identity is its name plus its defining loader, so they are different types; casting one to the other throws ClassCastException. Application servers use this to isolate web applications.
Practice problems
Class loading questions are mostly about order: what is initialised, when, and by whom. Predict every line of output before running. The last three are business tasks.
Static block vs main
Put a static block in Main and a print as the first line of main. Which prints first?
(no input)
Main initialised, visits = 10 main starts visits = 11
Show solution and explanation
public class Main {
static int visits;
static {
visits = 10;
System.out.println("Main initialised, visits = " + visits);
}
public static void main(String[] args) {
System.out.println("main starts");
visits++;
System.out.println("visits = " + visits);
}
}How it works: before calling main, the JVM must initialise class Main, because invoking a static method is an active use. So the static block always runs first, exactly once.
Which loader loaded it?
Print the class loader of String, java.sql.Date and your own Main class.
String, java.sql.Date, Main
String -> bootstrap (shown as null) java.sql.Date -> platform Main -> app Parent of app -> platform
Show solution and explanation
public class Main {
public static void main(String[] args) {
ClassLoader forString = String.class.getClassLoader();
ClassLoader forSqlDate = java.sql.Date.class.getClassLoader();
ClassLoader forMain = Main.class.getClassLoader();
System.out.println("String -> " + (forString == null ? "bootstrap (shown as null)" : forString.getName()));
System.out.println("java.sql.Date -> " + forSqlDate.getName());
System.out.println("Main -> " + forMain.getName());
System.out.println("Parent of app -> " + forMain.getParent().getName());
}
}How it works: core classes in java.base are loaded by the bootstrap loader, which is part of the JVM itself and is represented by null. Other platform modules such as java.sql use the platform loader (Java 9+). Your classes on the class path use the application ("app") loader, whose parent is the platform loader.
Reading a constant doesn't initialise
A class has a static final constant and a static block. Show that reading the constant does not run the block, but reading a normal static field does.
Limits.MAX_ITEMS, then Limits.discount
Max items: 20 Limits initialised Discount: 5%
Show solution and explanation
public class Main {
static class Limits {
static final int MAX_ITEMS = 20; // compile-time constant
static int discount = 5; // ordinary static field
static {
System.out.println("Limits initialised");
}
}
public static void main(String[] args) {
System.out.println("Max items: " + Limits.MAX_ITEMS);
System.out.println("Discount: " + Limits.discount + "%");
}
}How it works: a static final primitive or String initialised with a constant expression is a compile-time constant: javac copies its value into the code that reads it, so the class isn't touched. discount is a real field read with getstatic, which is an active use and triggers initialisation.
Child.field initialises only the parent
A static field is declared in Parent and read through Child. Which static blocks run?
Child.appName
Parent initialised Name: ShopEasy --- Child initialised Version: 2
Show solution and explanation
public class Main {
static class Parent {
static String appName = "ShopEasy";
static { System.out.println("Parent initialised"); }
}
static class Child extends Parent {
static { System.out.println("Child initialised"); }
static int version = 2;
}
public static void main(String[] args) {
System.out.println("Name: " + Child.appName);
System.out.println("---");
System.out.println("Version: " + Child.version);
}
}How it works: only the class that declares a static field is initialised when it is accessed, even if you write Child.appName. Child is initialised later, when its own field version is read. A favourite interview trick question.
Load a class by name, safely
Load classes by their names at run time with Class.forName. Report which exist instead of crashing.
"java.util.ArrayList", "com.shop.PaymentGateway", "java.time.LocalDate"
found: ArrayList missing: com.shop.PaymentGateway found: LocalDate
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] names = {"java.util.ArrayList", "com.shop.PaymentGateway", "java.time.LocalDate"};
for (String name : names) {
try {
Class<?> c = Class.forName(name);
System.out.println("found: " + c.getSimpleName());
} catch (ClassNotFoundException e) {
System.out.println("missing: " + e.getMessage());
}
}
}
}How it works: Class.forName asks the class loaders to find a class by its fully qualified name. If no loader can find it, you get the checked exception ClassNotFoundException, which you are expected to handle. That differs from NoClassDefFoundError, which happens when a class that existed at compile time is missing or broken at run time.
A bad config value breaks the class for good
A tax table reads its GST rate from a config string in a static initializer. The config contains "18%", which isn't a number. Show what happens on the first and second attempts to use the class.
config value = "18%"
attempt 1: java.lang.ExceptionInInitializerError cause: java.lang.NumberFormatException: For input string: "18%" attempt 2: java.lang.NoClassDefFoundError: Could not initialize class Main$TaxTable
Show solution and explanation
public class Main {
static class TaxTable {
static int gstPercent = Integer.parseInt("18%");
}
public static void main(String[] args) {
for (int attempt = 1; attempt <= 2; attempt++) {
try {
System.out.println("GST: " + TaxTable.gstPercent);
} catch (Throwable e) {
System.out.println("attempt " + attempt + ": " + e);
if (e instanceof ExceptionInInitializerError) {
System.out.println(" cause: " + e.getCause());
}
}
}
}
}How it works:
- The first use runs the static initializer, which throws
NumberFormatException. The JVM wraps it inExceptionInInitializerError. - The class is now marked as failed. Every later use throws
NoClassDefFoundError: Could not initialize class …, without retrying. In a server, this means one bad config value breaks the feature until a restart. - Validate configuration with clear errors before it reaches static initializers, or load it in a normal method that can be retried.
The discount that came out as zero
A shop's pricing class computes FESTIVE_PRICE from BASE_PRICE, but declares them in the wrong order. Show the bug, then the fixed order.
base price Rs 1000, festive discount 20%
Buggy festive price: Rs 0 Fixed festive price: Rs 800
Show solution and explanation
public class Main {
static class BuggyPricing {
static int festivePrice = discounted(); // runs first
static int basePrice = 1000; // not set yet!
static int discounted() {
return basePrice * 80 / 100;
}
}
static class FixedPricing {
static int basePrice = 1000; // declared first
static int festivePrice = discounted();
static int discounted() {
return basePrice * 80 / 100;
}
}
public static void main(String[] args) {
System.out.println("Buggy festive price: Rs " + BuggyPricing.festivePrice);
System.out.println("Fixed festive price: Rs " + FixedPricing.festivePrice);
}
}How it works:
- During preparation, every static field gets its default value (0). During initialisation, the initializers run top to bottom.
- In BuggyPricing,
discounted()runs whilebasePriceis still 0, so the festive price becomes 0, and stays 0 because it is computed only once. - Declaring
basePricefirst fixes it. Making itstatic final int BASE_PRICE = 1000(a constant) would also fix it, since constants are set before any initializer runs.
Payment providers as plug-ins
A checkout reads the list of enabled payment providers (class names) from configuration and creates each one by name. Unknown providers must be skipped with a warning.
enabled = "Main$UpiProvider", "Main$CardProvider", "Main$CryptoProvider"
UPI paid Rs 499 Card paid Rs 499 skipped unknown provider Main$CryptoProvider
Show solution and explanation
public class Main {
public interface Provider {
String pay(int amount);
}
public static class UpiProvider implements Provider {
public String pay(int amount) { return "UPI paid Rs " + amount; }
}
public static class CardProvider implements Provider {
public String pay(int amount) { return "Card paid Rs " + amount; }
}
public static void main(String[] args) throws Exception {
String[] enabled = {"Main$UpiProvider", "Main$CardProvider", "Main$CryptoProvider"};
for (String className : enabled) {
try {
Class<?> c = Class.forName(className);
Provider p = (Provider) c.getDeclaredConstructor().newInstance();
System.out.println(p.pay(499));
} catch (ClassNotFoundException e) {
System.out.println("skipped unknown provider " + e.getMessage());
}
}
}
}How it works:
- Nested classes have binary names with a
$, such asMain$UpiProvider. Class.forNameloads and initialises the class;getDeclaredConstructor().newInstance()creates an object without anewin the source. This is how JDBC drivers, logging back-ends and framework plug-ins were traditionally wired in.- A missing provider produces
ClassNotFoundException, which we turn into a warning instead of a failed checkout.
Real-world scenario questions (asked in company interviews)
Class loading problems usually appear at deploy time, under pressure. Decide your answer first, then open the model answer.
Works on my machine, NoClassDefFoundError on the server
A loan-calculator service runs fine in the IDE. On the test server, the first EMI request fails with java.lang.NoClassDefFoundError: com/acme/util/MoneyFormatter. Start-up was fine. What is the likely cause and how do you check?
The interviewer is checking: do you know classes load lazily and what NCDFE means?
Show a strong answer
- The code was compiled with
MoneyFormatteravailable, but at run time the JAR containing it is missing from the class path. Because classes load lazily, the error appears only when the first EMI request needs the class, not at start-up. - Check the deployed package or class path (for example list the JAR's contents), and the build's dependency scope: a library marked “provided” or “test” is not packaged.
- Also check the full stack trace: if it says “Could not initialize class”, the class exists but its static initializer failed earlier; look for the first
ExceptionInInitializerErrorin the logs.
Avoid: catching the error and continuing.
One bad property killed checkout until restart
A PricingRules class loads a properties file in a static block. After an ops change, a value became "15 percent". The first checkout failed with ExceptionInInitializerError; every checkout after that failed with NoClassDefFoundError, even after the file was corrected. Why didn't fixing the file help, and how would you redesign it?
The interviewer is checking: do you know a class is initialised only once, and that failure is permanent?
Show a strong answer
- The static initializer threw
NumberFormatException, so the JVM markedPricingRulesas failed. It never re-runs a class's initializer, so the corrected file was never read; only a restart helps. - Redesign: load configuration in an ordinary method or a managed bean at start-up, validate every value with clear messages, and fail fast at start-up rather than on the first customer request.
- For values that can change, support reloading with validation, keeping the last good configuration if the new one is invalid.
Avoid: putting I/O or parsing that can fail inside a static block.
Metaspace grows with every redeploy
An internal SaaS platform hot-redeploys a web app on Tomcat several times a day. After about 20 redeploys the server dies with OutOfMemoryError: Metaspace. Heap usage looks normal. Explain the mechanism and how you would find the cause.
The interviewer is checking: do you understand that classes are unloaded only when their class loader becomes unreachable?
Show a strong answer
- Each deployment gets a new class loader, which loads its own copies of all the app's classes into metaspace. The old classes can be unloaded only when the old loader, every class it loaded and every object of those classes are unreachable.
- Something outside the app still references the old loader: a JDBC driver registered in
DriverManager, a thread the app started and never stopped, aThreadLocalvalue on a server thread, or a static cache in a shared library. - Take a heap dump after a few redeploys, look for multiple web-app class loader instances, and follow the path to GC roots. Fix by deregistering drivers, stopping threads and clearing thread-locals on shutdown. Cap metaspace with
-XX:MaxMetaspaceSizeto fail early and visibly.
Avoid: simply raising the metaspace limit.
Lazy and thread-safe without locks
A payment SDK must create its expensive GatewayClient only when the first payment happens, and many threads may call getInstance() at the same moment. A colleague wrote a synchronized method with an if (instance == null) check. What alternative uses class loading rules, and why is it safe?
The interviewer is checking: can you apply initialisation guarantees to a real design?
Show a strong answer
- Use the holder idiom:
private static class Holder { static final GatewayClient INSTANCE = new GatewayClient(); }andgetInstance()returnsHolder.INSTANCE. - Holder is initialised on its first active use, the first call to
getInstance(), so creation is lazy. - The JVM initialises a class under a lock and only once; other threads wait and then see the fully built instance. No
synchronizedon every call, so no lock cost afterwards. - An
enumwith a single constant is another safe option.
Avoid: double-checked locking without volatile; it is broken.