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.
59 questionsLast updated
An object of at least half a region is humongous. G1 places it directly into one or more contiguous free regions, so it skips eden and can waste the unused part of its last region. Many large short-lived arrays can fill the heap quickly and trigger extra collections; larger regions or smaller objects help.
Lesson: How G1 worksNot as a first step. A lower goal makes G1 collect more often with a smaller eden, which raises total GC work and lowers throughput, and very low goals are often missed anyway. I would first read the GC logs to see whether pauses really are the problem, and if a much lower pause is truly needed, measure ZGC as well.
Lesson: How G1 worksI would use -XX:MaxRAMPercentage, often around 70 to 75, so the heap follows the container limit, instead of a fixed -Xmx. I would leave room for stacks, metaspace, code cache and other native memory, because going over the container limit gets the process killed. Then I would check the real values with jcmd <pid> VM.flags and watch GC logs under load.
No. Metaspace holds class information in native memory outside the heap, and since Java 8 it replaced the old PermGen. By default it has no limit, so I cap it with -XX:MaxMetaspaceSize when I want a class-loading leak to fail fast with OutOfMemoryError: Metaspace.
With unified logging: -Xlog:gc for one line per collection, or -Xlog:gc* for full detail. To keep it, I send it to a file with rotation, for example "-Xlog:gc*:file=gc-%p.log:time,uptime,level,tags:filecount=5,filesize=20m".
Yes, for -Xlog:gc or -Xlog:gc* at the info level: it reports what the collector already did and writes only a few lines per collection. I write it to a file with rotation so it cannot fill the disk, and avoid debug or trace levels except for a short investigation.
The default uptime only says seconds since the JVM started. With time each line also has the wall-clock date and time, so I can match a long pause with a user complaint or an alert at 10:02 am without working out when the JVM started.
I watch the heap-after-GC numbers over time. In a leak they keep rising even after mixed or full collections, collections come closer together, and eventually Full GCs appear that free almost nothing. A flat floor with a sawtooth above it means memory is being freed normally.
Lesson: Reading a GC logNo. With Serial and Parallel, "Allocation Failure" just means eden was full when the program tried to allocate, which is the normal reason for a young collection. I would look at the heap after GC and the pause times instead.
Lesson: Reading a GC logNot before I know why. I would check the GC log and a heap dump: if the heap after GC keeps rising, it is a leak and more heap only delays the crash, so I fix the code. If the heap after GC is high but flat, the program really needs that data, and raising -Xmx or MaxRAMPercentage is the right fix.
Use primitive arrays or primitive streams instead of boxed collections in hot loops, build strings with StringBuilder instead of += in loops, and avoid copying large lists or arrays just to read part of them. I only do this where a profiler or measurements show a hot spot, to keep the code simple elsewhere.
A heap dump is a snapshot of every object in the heap. Opened in a tool such as Eclipse MAT or VisualVM, it shows which classes use the most memory and the chain of references keeping them alive, for example a static map holding millions of sessions. That points straight to the code to fix.
Lesson: When to change settings and when to fix the codeI would run the same build with the same heap and a realistic load, changing only the collector flag, with GC logs on. I would repeat each run several times and compare the metrics that matter for the app: the longest or 99th-percentile pause for a user-facing service, the total GC time for a batch job, plus CPU and memory use.
Lesson: Garbage collectors and options practice-XX:MaxRAMPercentage=75 so the heap follows the container limit, -XX:+HeapDumpOnOutOfMemoryError with a HeapDumpPath on a mounted volume, and -Xlog:gc* to a rotating file with the time decorator. I would keep G1 with default settings and check the result once with jcmd <pid> VM.flags.
If the heap after GC is flat but close to the maximum, with frequent marking or Full GCs, the live data is real and more heap helps. If the heap after GC keeps rising over time, it is a leak, and more heap only delays the failure; the code needs fixing.
Lesson: Garbage collectors and options practiceIn C a leak is memory you allocated and forgot to free, and nothing points to it any more. In Java such unreachable memory is collected automatically; a Java leak is the opposite, memory that is still referenced but logically dead.
Not necessarily. It shows the allocation that could not find room, which is often an innocent new somewhere else. To find who is holding the memory you need a heap dump and the path from the big objects to the GC roots.
System.gc() fix a memory leak?No. System.gc() is only a request to collect garbage, and leaked objects are not garbage because they are still reachable. The fix is in the code: remove the reference, keep less data or bound the collection.
A weak reference does not keep its object alive at all, so it is usually cleared at the next GC that examines the object. A soft reference keeps it while memory allows and is cleared only under memory pressure, and always before an OutOfMemoryError. On HotSpot the soft policy depends on free heap and how recently the object was used.
Lesson: Strong, soft, weak and phantom referencesif (ref.get() != null) ref.get().doWork(); still fail?Because the GC can clear the reference between the two get() calls, so the second one returns null. The fix is to call get() once, keep the result in a local variable, which is a strong reference, and check and use only that local.
It stores each key inside a WeakReference registered on a ReferenceQueue. When a key is no longer strongly reachable, the GC clears it and queues the reference, and the map removes the matching entries the next time one of its methods runs. Values are held strongly.
Either the keys are always reachable somewhere else, like string literals, small cached Integers or enum constants, or the values hold references to their own keys. In the second case the map's strong value reference keeps the key alive, so the entry can never be cleared.
No. In access order even get rearranges the internal list, so two threads can corrupt it. I would wrap it with Collections.synchronizedMap for light use, or use a proper concurrent cache library such as Caffeine for a busy service.
Because it gives no guarantee about when or even whether it runs, it runs on a JVM thread you do not control, it delays memory reclamation by at least one extra GC cycle, it ignores exceptions and it allows resurrection of objects. It was deprecated in Java 9 and marked for removal in Java 18 by JEP 421.
Lesson: Cleaner and cleaning up resourcesLetting the cleaning action refer to the object being cleaned, for example a lambda that reads this.field, or an inner class. Then the Cleaner keeps the object reachable, so it is never cleaned and it leaks. Use a static nested class or capture only copied values.
In reverse order of declaration. If the body already threw, the close exception is added to it as a suppressed exception, so the original error is not lost; if the body succeeded, the close exception is thrown normally.
Lesson: Cleaner and cleaning up resourcesIf it has no size limit and no expiry, it grows with every distinct key it has seen. With keys like session ids, guest tokens or anything containing a timestamp, the number of distinct keys never stops growing, so neither does the cache.
Lesson: Leaks from static collections and cachesMake it immutable: final fields, no setters, with equals and hashCode based on those fields. A String or Integer id or a record works well. If the domain object is mutable, key by its stable id instead of the object.
Removal on logout for the normal case, idle-time expiry for users who just close the app, and a maximum size with least-recently-used eviction as a final guard. Together they bound memory even under unexpected traffic.
Lesson: Leaks from static collections and cachesMostly it causes a resource leak: the file handle or socket stays open until the object is collected, if ever, so the OS limit is hit first with errors like Too many open files. Buffers and objects tied to the stream also stay in memory while it is referenced. try-with-resources closes them on every path.
Each pool's threads wait for work for ever, so the thread count grows with every request. Each thread costs a native stack and keeps its tasks' data reachable; eventually the JVM throws OutOfMemoryError: unable to create native thread, and meanwhile the non-daemon threads stop the JVM from exiting.
A non-static inner class object holds a hidden reference to its outer object, the this$0 field. If the inner object is stored somewhere long-lived, such as a static list, a cache or a listener registry, the outer object and everything it references stay reachable too, even after the outer object is no longer used.
Only if they use it. A lambda captures this only when its body uses an instance field, an instance method or this; otherwise it captures just the local values it uses. An anonymous class created in an instance method always had the outer reference when compiled for Java 17 or earlier.
Only partly. javac 18 and later, targeting Java 18 or newer, omits this$0 when the inner or anonymous class never uses the outer object. As soon as it reads an outer field or calls an outer method, the reference is there on every version, so the design rule still applies.
Yes. The GC frees only unreachable objects, so anything still referenced but no longer needed leaks. A classic example is a static Map<String, Session> that gets a session on every login, while logout forgets to remove it: the map grows until OutOfMemoryError: Java heap space.
Values live in the thread, and server threads are pooled and live as long as the server. A value that is not removed stays reachable, and if its class came from a web application, it can also keep that application's class loader and all its classes in memory after the application is redeployed. remove() in finally prevents both.
Yes. An inner or anonymous class made in an instance method holds a hidden reference to the outer object, and a lambda or this::method holds this when it uses instance members. If such an object is registered somewhere long-lived, the outer object stays in memory; static nested classes and copied local values avoid it.
jcmd sends diagnostic commands to a running JVM: version, flags, properties, heap info, histograms, heap and thread dumps, and Flight Recorder control. It replaces several older single-purpose tools with one entry point, and jcmd <pid> help shows exactly what that JVM version supports.
Most are, such as VM.version, VM.flags and GC.heap_info. Some are heavy: GC.class_histogram and GC.heap_dump run a full GC and pause the application, and their help text says "Impact: High". I check jcmd <pid> help <command> and pick a quiet moment for heavy ones.
jstat prints a running JVM's heap and GC counters at a fixed interval: eden, survivor, old and metaspace sizes, and the number and time of young and full collections. I use it to watch memory over minutes or hours, because it is cheap enough to run on production.
I take two lines some time apart and subtract GCT, the total GC time in seconds, then divide by the seconds between the lines. For example 0.6 seconds of GC in 60 seconds is 1%. One line alone is useless because the counters are totals since start-up.
Yes. ManagementFactory.getGarbageCollectorMXBeans() gives each collector's count and time, and MemoryMXBean and MemoryPoolMXBean give heap and pool usage. Monitoring agents read these beans, often over JMX, and send them to tools like Prometheus or Grafana.
I start every service with -XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath pointing to a folder with free space at least the size of -Xmx. The flags cost nothing until the error happens. If a running JVM lacks them, I can set them with jcmd <pid> VM.set_flag.
They contain every live object, so passwords, tokens and customer data are in the file as plain bytes. I keep dumps in restricted storage, never attach them to tickets or public issues, compress them before transfer, and delete them when the investigation is done. In code, I clear char[] passwords after use and avoid keeping secrets in long-lived objects.
I open the dump in a tool like Eclipse MAT, check the histogram for my own classes with odd counts, then look at the dominator tree or Leak Suspects report for objects with the biggest retained size. On a suspect I show the path to GC roots, excluding weak and soft references, and the field next to the root tells me which code to fix. A second dump later confirms that the same classes keep growing.
Lesson: Reading a heap dumpOne dump cannot tell a leak from a program that simply needs a lot of memory at that moment. Two dumps taken some time apart under similar load show which classes keep growing. Classes that grow together, such as an event class plus the same number of strings, point to the object that is being kept.
Lesson: Reading a heap dumpA thread dump is the stack trace, state and lock information of every live thread at one moment. I take it with jcmd <pid> Thread.print or jstack <pid>, or with kill -3 <pid> on Linux, which prints it to the JVM's own output. I usually take three, a few seconds apart.
Take locks in one global order everywhere, for example by account id in a money transfer. Keep critical sections short and avoid calling unknown code while holding a lock. Where possible, use tryLock with a timeout, or higher-level tools such as concurrent collections that need no manual locking.
One thread showing the same frame hundreds of times is recursion that goes too deep, for example a tree with a cycle. It will end in StackOverflowError if it keeps going. I fix the base case or the data, or rewrite the recursion with an explicit stack; raising -Xss only hides the bug.
With jcmd <pid> JFR.start name=rec, optionally with settings=profile and a duration or maxsize. JFR.dump name=rec filename=rec.jfr writes the data so far and JFR.stop ends it. At start-up, -XX:StartFlightRecording=... does the same.
I would keep a continuous recording running with a size limit, for example -XX:StartFlightRecording=maxsize=200m, and dump it right after the slowdown. In JMC I would check automated analysis, then GC pauses, hot methods, lock contention and I/O events around 10 am. The recording shows the minutes before the problem, which a tool attached later cannot.
It is thrown by HotSpot's Parallel collector when the JVM spends about 98% of its time in GC and recovers less than 2% of the heap, several times in a row. It fails fast instead of letting the program crawl. With G1, the default on JDK 21, the same situation ends in Java heap space.
Usually not. The error is thrown wherever memory is requested when the heap is already full, so the trace shows the last allocation. The objects filling the heap may have been created hours earlier elsewhere. I use a heap dump, retained sizes and paths to GC roots to find the real holder.
Lesson: Reading an OutOfMemoryError messageExiting gives a clean restart instead of a half-broken service, but a restart alone destroys the evidence. With both flags, the JVM first writes the heap dump and then exits, so the service recovers quickly and I still have the dump to find the leak.
Lesson: Reading an OutOfMemoryError messageA leak keeps raising the heap after GC as time passes, even under steady load, until it fails. A program that needs more memory reaches a high but stable level. If the live data is legitimately large, I size the heap for it; if it grows without limit, I find what is kept.
Lesson: A step-by-step diagnosis workflowObserve: logs and the OutOfMemoryError message. Measure: -Xlog:gc, jstat -gcutil, jcmd GC.heap_info and VM.flags. Capture: jcmd GC.heap_dump or HeapDumpOnOutOfMemoryError, Thread.print, and JFR. Analyse: Eclipse MAT or VisualVM for dumps and JMC for recordings.
jps to find the JVM, jcmd for almost everything else (flags, heap info, histograms, heap and thread dumps, Flight Recorder), and jstat to watch GC counters over time. jmap and jstack are older tools for histograms, heap dumps and thread dumps; jcmd covers both.
Shallow heap is the size of the object itself. Retained heap is everything that would be freed if that object were collected, including objects reachable only through it. Leaks are found by retained size, because the culprit is usually a small collection object holding a huge amount.
Lesson: Diagnosing memory problems practiceYes, that is what it is designed for. With default settings the overhead is usually around 1% or less, and it has been free in OpenJDK since version 11. I keep a continuous recording with a size limit and dump it with jcmd <pid> JFR.dump when something goes wrong.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson