Loading...
Loading...
Java interview guide
Class loading, memory areas, garbage collection and performance basics. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
61 questionsLast updated
The bytecode is platform independent: the same .class file runs on any compatible JVM. The JVM itself is platform specific; there are separate builds for Windows, Linux and macOS, and for x64 and ARM.
In the usual simplified model: the class loader subsystem, the runtime data areas (heap, method area, one stack per thread, plus program counters and native method stacks), the execution engine with interpreter and JIT compilers, and the garbage collector.
Lesson: From .java file to running programCAFEBABE?The magic number: the first four bytes of every class file. The JVM checks it before anything else and throws ClassFormatError if it is wrong. It is just a fixed marker chosen by the original Java team.
One per class or interface declared in it, including nested, inner, local and anonymous classes: Outer$Inner.class, Outer$1.class, Outer$1Local.class. Lambdas do not get class files from javac on Java 8 and later; their bodies become private synthetic methods and the classes are created at run time.
javac adds some members itself. Examples are the default constructor when you write none, bridge methods such as compareTo(Object) for generic interfaces, and private lambda$... methods that hold lambda bodies. They are marked synthetic or bridge in the class file.
long written as J and boolean as Z?Because L is already used to start class types such as Ljava/lang/String;, and B is taken by byte. So the format picked other letters: J for long and Z for boolean.
public static final int constant in a library is enough to update all apps using it. Is that right?Not without recompiling them. javac inlines compile-time constants into the code that uses them, as javap -c shows (sipush 5000 instead of a field read). Apps compiled against the old value keep the old number until they are recompiled.
In the JVM's model, a frame has a local variable array (parameters and locals, plus this in slot 0 for instance methods), an operand stack, and a reference to the class's constant pool for resolving symbols. A frame is created on each method call and removed when the method returns or throws.
Not for code that runs often. The stack is the bytecode's model; HotSpot's JIT compiles hot methods into register-based machine code for the actual CPU. Interpretation is slower, but only cold code stays interpreted.
Lesson: How bytecode works: the operand stackinvokevirtual, invokeinterface, invokestatic and invokespecial?invokestatic calls a static method with no receiver. invokevirtual and invokeinterface call instance methods through a class or interface type, with the override chosen from the object's runtime class. invokespecial calls one exact method with no override lookup, used for constructors and super calls (and, before Java 11, private methods).
Integer a = 128, b = 128; a == b give false but 127 gives true?Autoboxing calls Integer.valueOf, which returns cached objects for -128 to 127. 127 reuses one cached object, so the references are equal; 128 creates two objects. == on wrappers compares references, so the fix is equals or comparing the unboxed int values.
NullPointerException come from in int total = map.get(key) + 5;?From the hidden unboxing. If the key is missing, get returns null and javac's inserted intValue() call throws. getOrDefault(key, 0) or a null check avoids it.
Find the pid with jps -l, then run jcmd <pid> Thread.print (or the older jstack <pid>). It prints every thread's state and stack, which shows deadlocks and threads stuck waiting.
It is an event recorder built into HotSpot that captures GC, allocation, locking, I/O, CPU and many other events into a .jfr file. Its overhead is low enough for production use in its default settings, and it has been free in OpenJDK since Java 11. You start it with jcmd <pid> JFR.start or the -XX:StartFlightRecording option and analyse the file with jfr or JDK Mission Control.
Compile with a known target, for example javac --release 17 -g App.java, then run javap -c -p App and find the method. I map each group of instructions back to a source line, using javap -l for the exact line numbers when needed.
== between two Integer values work in tests and fail in production?Autoboxing uses Integer.valueOf, which caches -128 to 127. Tests with small numbers get the same cached objects, so == is true; real data with bigger numbers gets separate objects and == compares references. Wrappers must be compared with equals.
UnsupportedClassVersionError: class file version 65.0 ... up to 61.0. Walk me through it.65 − 44 = 21 and 61 − 44 = 17, so classes built for Java 21 are running on a Java 17 JVM. Either rebuild with --release 17 (in Maven, maven.compiler.release), checking that no Java 21 APIs are used, or upgrade the runtime to Java 21. I would also add a build check so it cannot happen again.
No. HotSpot loads classes lazily: the main class first, then each class the first time the code needs it. A class the code never reaches is never loaded. Many JDK classes are loaded at start-up, but that is because the JVM itself needs them.
Lesson: What class loading meansClass object? How do you get one?It is the java.lang.Class object the JVM creates for each loaded class, and it describes the class: name, superclass, methods and so on. I can get it with a class literal like Student.class, with obj.getClass(), or with Class.forName("Student"). All three return the same object.
It keeps start-up time and memory low, because features nobody uses never get loaded. It also means a missing class causes an error only when the code first needs it, not at start-up, which is why some class path problems appear late in a run.
Lesson: What class loading meansBecause the JVM cannot trust where a class file came from. It may be old, compiled against a different version of another class, hand-made or changed by a tool. The verifier makes sure bad bytecode cannot corrupt the stack or memory, which is part of what makes Java safe.
Lesson: Loading, linking and initializationThe JVM specification allows either early or lazy resolution. HotSpot resolves most references lazily, the first time an instruction that uses them runs. So a missing class used only on a rare path may cause an error late in a run.
Lesson: Loading, linking and initializationThere are three. The bootstrap loader is built into the JVM and loads core classes such as String. The platform loader loads the other JDK modules, for example java.sql. The application, or system, loader loads my classes and libraries from the class path and module path.
No. Visibility goes one way: a loader can see classes of its parents, but a parent cannot see classes of its children. String can never refer to Student; only the reverse works.
Every thread carries a class loader reference, returned by Thread.currentThread().getContextClassLoader(); by default it is the application loader. Framework code loaded by a parent loader uses it to load application classes it cannot see directly, for example when finding plug-in implementations by name.
java.lang.String and make Java use it?No. On Java 9 and later javac refuses to compile into java.lang because the java.base module owns it. Even a class file compiled the old way is never used, because delegation reaches the bootstrap loader first, and defineClass throws SecurityException for any java.* name from a normal loader.
A child-first loader tries its own classes before asking the parent, by overriding loadClass. Servlet containers such as Tomcat do this for each web application, so one app can use its own version of a library even if the server has another. They still delegate java.* and other core classes to the parent first.
Right before its first active use: creating an instance, calling a static method, reading or writing a static field that is not a compile-time constant, Class.forName with one argument, or reflective use such as invoking a static method. Initializing a subclass also initializes its superclass first, and the main class is initialized before main.
Not just because a class implements it. An interface is initialized when one of its own non-constant fields is read, or together with an implementing class if the interface declares a default method. Interfaces with only abstract methods and constants are often never initialized at all.
synchronized?The instance lives in a static field of a nested Holder class. Holder is initialized only when getInstance() first reads that field, so creation is lazy. And the JVM guarantees that class initialization runs once, with other threads waiting, so it is thread-safe for free.
NoClassDefFoundError often say Caused by: ClassNotFoundException?When the JVM needs a class, it asks the class loader, which throws ClassNotFoundException if the lookup fails. The JVM wraps that in NoClassDefFoundError, because for compiled code a missing class is a linkage problem. So the cause is the internal lookup, not a Class.forName call in my code.
NoClassDefFoundError when you run java -jar. Why?The IDE builds the class path from all project dependencies automatically. java -jar uses only the JAR and the Class-Path in its manifest, and it ignores -cp. I either add the libraries to the manifest Class-Path, or build a single JAR that contains them, for example with a Maven or Gradle plugin.
Inside the program, System.getProperty("java.class.path") shows it. From outside, jcmd <pid> VM.system_properties lists java.class.path. With -Xlog:class+load I can also see the exact folder or JAR each class came from.
The object's class and the target class have the same name but were defined by different loaders; the message names both loaders. The fix is to load that type once, in a parent loader that both sides delegate to, usually by putting the shared interface in a common API JAR and keeping it out of the plugin JARs.
Lesson: Custom class loaders and class identityIt happens when something outside the old loader still references it after a redeploy, for example a static map in a parent-loaded class holding an app object, a thread started by the app, or a registered listener. The loader and every class it defined then stay in Metaspace. Each redeploy adds another copy, until OutOfMemoryError: Metaspace.
java App until main runs, as far as classes are concerned.The JVM starts, the bootstrap loader brings in core classes (on HotSpot many come from the CDS archive), and the application loader is set up from the class path. It then loads App (delegating up the chain first), links it by verifying it and preparing its static fields, and initializes it by running its static initializer. Only then does main run, and every other class is loaded lazily on first use.
ClassNotFoundException or NoClassDefFoundError: how do you tell them apart in a log, and fix each?ClassNotFoundException comes from code loading a class by name; I fix the name or add the library. NoClassDefFoundError: x/Y means compiled code needs Y but it is missing at run time; I fix the run-time class path or packaging. If the message is Could not initialize class Y, the class exists and I look for the first ExceptionInInitializerError instead.
I run, or restart, with -Xlog:class+load (or -verbose:class) and look at the source of one of the library's classes, which names the exact JAR. Usually two versions are on the class path and the first one wins. I then fix the dependencies, for example by excluding the old version in Maven or Gradle.
A just-in-time compiler translates bytecode to machine code while the program runs, only for code that turns out to be hot. It gives near-native speed for the code that matters while keeping fast startup and portable bytecode. It can also optimise using what it saw at run time, such as which branches are actually taken.
Lesson: The interpreter and the JIT compilerIt lives in the code cache, an area of native memory outside the Java heap. On HotSpot 17 and 21 its maximum size is 240 MB by default with tiered compilation, set by -XX:ReservedCodeCacheSize. If it fills up, the JIT stops compiling new code and the application keeps running more slowly.
-Xint do, and would you use it in production?-Xint makes the JVM interpret everything and switches the JIT off. I would use it only in experiments, for example to show how much the JIT helps. In production it would make hot code many times slower.
It means a method moves through levels: interpreter (0), C1 with profiling (3), then C2 (4); trivial methods may stop at level 1. It is the default since Java 8 because it gets both good startup, from C1, and good peak speed, from C2, instead of choosing one.
Lesson: Hot methods and tiered compilationmade not entrant in a PrintCompilation log. Is something wrong?Not necessarily. Most often it is the level 3 version being retired after the level 4 version is ready, which is normal. If a level 4 version is made not entrant, the JIT has thrown away optimised code, usually because an assumption broke; that is deoptimization, and it only matters if it keeps repeating.
Lesson: Hot methods and tiered compilationInlining is when the JIT replaces a call with the body of the called method inside the compiled code. It removes the call overhead and, more importantly, lets the compiler optimise the caller and callee together. The source code and bytecode do not change, and neither does the behaviour.
Lesson: Method inliningNo. Static, private and final methods are easy, but C2 also inlines virtual and interface calls using the type profile: one or two receiver types seen at a call site get inlined behind a cheap type check. With three or more types it usually keeps a virtual call, unless one type covers about 90 percent of calls.
Lesson: Method inliningRun with -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining, usually together with -XX:+PrintCompilation. Under each compiled method it lists every call by bytecode position with a decision such as inline (hot), accessor, too big or virtual call. JITWatch can show the same log in a friendlier way.
It is an analysis the C2 JIT runs on hot methods to find objects that never become reachable outside the compiled method or the current thread. For such objects it can skip the heap allocation (scalar replacement) and remove unneeded locks. It runs after inlining, so small helper methods do not block it.
Lesson: Escape analysis in simple termsStringBuffer therefore as fast as StringBuilder?Lock elision removes synchronized locking on objects that only one thread can see, like a local StringBuffer. After C2 compiles the code they can perform similarly, but that depends on the optimisation succeeding. For single-thread building I use StringBuilder, which has no locks to remove.
Measure allocation, for example with getCurrentThreadAllocatedBytes() from com.sun.management.ThreadMXBean, or with JDK Flight Recorder allocation events. Comparing a run with -XX:-DoEscapeAnalysis shows what it saves. I would never decide from reading the code alone.
Optimising for what actually happens gives much faster code: skipping branches that never run, inlining the only type seen at a call. It is not risky because every guess is protected by an uncommon trap or a recorded dependency; if the guess fails, the JVM deoptimizes instead of giving a wrong result.
Lesson: Deoptimization: when the JIT changes its mindRun with -XX:+PrintCompilation to see which method is made not entrant repeatedly, and with -Xlog:deoptimization=debug to see the trap reason and bytecode index, such as unstable_if or class_check. Then look at that branch or call site in the code. JDK Flight Recorder also records deoptimization events.
final help the JIT avoid deoptimization?Rarely in a way that matters. HotSpot already uses class hierarchy analysis and type profiles, so a non-final class with one implementation is optimised just as well. I mark classes final for design reasons, not for JIT speed.
System.nanoTime()?One run may be interpreted, may include class loading or a background compile, and may be hit by a GC pause or the OS. The number has huge noise and usually reflects the cold state, not the steady speed. Comparing two such numbers can easily point the wrong way.
Lesson: JIT warm-up and honest timing testsIt runs warm-up iterations, measures in fresh forked JVMs, protects results from dead code removal, handles timer details and reports a score with an error range. It also has modes for average time, throughput and more. It is the OpenJDK team's own tool for this job.
Lesson: JIT warm-up and honest timing testsBoth. javac compiles source to bytecode. At run time HotSpot starts by interpreting it, counts which methods and loops are hot, and compiles those to machine code with C1 and then C2. So a long-running Java program spends most of its time in JIT-compiled machine code.
Whether it warmed up both versions, measured many runs instead of one, used the results so the JIT could not remove the work, and kept printing or other extra work out of the timed part. I would also ask for the median and the spread, and for anything important, a JMH benchmark with forks.
Lesson: Execution engine practiceAfter a restart the request-handling code runs in the interpreter and C1 until C2 compiles it, so responses are slower during warm-up. I would send realistic warm-up traffic to each new instance before it joins the load balancer, and check that class data sharing is on to shorten startup.
Lesson: Execution engine practiceThe heap and the method area are shared, so any thread with a reference can use an object. Each thread has its own JVM stack, program counter and native method stack. That is why local variables are private to a call, while objects need care when several threads change them.
Lesson: The JVM memory mapIt stores the address of the bytecode instruction the thread will run next. Every thread has its own, because threads run different code at the same moment. While a thread is inside a native method, the JVM specification says its value is undefined.
Lesson: The JVM memory mapNo. The JVM specification describes it as a separate area, but HotSpot uses one stack per thread for both Java frames and native frames. Interview answers should name both areas from the specification and mention that HotSpot merges them.
Lesson: The JVM memory mapThe operand stack is a scratch stack inside each frame. Bytecode instructions pop their inputs from it and push their results, for example iadd pops two ints and pushes the sum. Because instructions work on this stack instead of named registers, the JVM's instruction set is called stack-based.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson