Lesson 70 of 71
JVM, JIT, GC, and Performance
Interview questions for senior and lead engineers on sections 11 to 16 of the course: JVM internals, JIT compilation, garbage collection, off-heap memory, Linux for Java, and performance tooling. Numbers are JDK 25 defaults on Linux x64, as measured in the linked lessons.
| Group | Lessons |
|---|---|
| JVM internals | Bytecode and class loading, Object layout, Runtime data areas, Safepoints and warmup |
| JIT | Tiered compilation and inlining, Escape analysis and assembly |
| GC | Fundamentals, G1, ZGC and Shenandoah, Selection and tuning |
| Off-heap | ByteBuffer and mmap, FFM API |
| Linux | Processes and scheduling, Virtual memory and perf |
| Tooling | JMH, JFR and async-profiler, Latency measurement |
Bytecode and class loading
1. A service fails at startup with UnsupportedClassVersionError. What happened?
A class was compiled for a newer Java than the runtime. The class file major version names the target: 65 is Java 21, 69 is Java 25. Recompile with --release for the oldest runtime, or upgrade the runtime.
2. Name the five invoke instructions.
invokevirtual (class methods, dispatched through the vtable), invokeinterface (interface tables), invokespecial (constructors, super calls, private methods in older class files), invokestatic (no receiver), and invokedynamic (linked once per call site by a bootstrap method).
3. What uses invokedynamic?
Lambdas (LambdaMetafactory.metafactory) and string concatenation since Java 9 (StringConcatFactory.makeConcatWithConstants). The bootstrap runs once per call site; later calls go through the linked target. javap -v shows the bootstrap methods.
4. What are the phases of the class lifecycle?
Loading, linking (verification, preparation, resolution), and initialization. Initialization runs <clinit> on first active use; compile-time constants do not trigger it, and Class.forName(name, false, loader) loads without initializing.
5. Show that reading a constant does not initialize a class.
public class InitTrigger {
static class Config {
static final int LIMIT = 10;
static final String NAME = name();
static { System.out.println("Config initialized"); }
static String name() { return "cfg"; }
}
public static void main(String[] args) {
System.out.println("limit " + Config.LIMIT);
System.out.println("name " + Config.NAME);
}
}limit 10
Config initialized
name cfgLIMIT is a compile-time constant copied into the caller's constant pool. NAME needs a method call, so reading it initializes Config first.
6. Spot the bug: the first call fails with one error, later calls with another.
public class FailedInit {
static class Broken {
static final int VALUE = Integer.parseInt("x");
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
try {
System.out.println(Broken.VALUE);
} catch (Throwable t) {
System.out.println(t.getClass().getSimpleName());
}
}
}
}ExceptionInInitializerError
NoClassDefFoundErrorA failed <clinit> throws ExceptionInInitializerError once and marks the class erroneous; every later use throws NoClassDefFoundError. In logs, search for the first error, which holds the cause.
7. Why can Foo not be cast to Foo?
Class identity is name plus defining loader. The same bytes loaded by two loaders form two classes, and casting between them throws ClassCastException. The usual loaders are bootstrap, platform, and application, with parent-first delegation; plugin and application-server loaders break that rule.
8. Follow-up: how can class initialization deadlock?
Two classes whose static initializers reference each other, initialized from two threads at once. Each thread holds one class's initialization lock and waits for the other.
Object layout and memory areas
9. How big is new Object(), an Integer, and a Long?
The header is an 8-byte mark word plus a 4-byte compressed class pointer, 12 bytes; arrays add a 4-byte length. Objects are padded to 8 bytes. new Object() takes 16 bytes, an Integer 16, a Long 24 by default.
10. What are compressed oops, and when are they lost?
References stored in 4 bytes, shifted to address up to about 32 GB of heap. Above that every reference becomes 8 bytes, so a 33 GB heap can hold less data than a 31 GB heap.
11. What are compact object headers?
An 8-byte header that merges the class pointer into the mark word: JEP 450 experimental in Java 24, JEP 519 product in Java 25, off by default.
12. Why is allocation cheap in Java?
Each thread allocates in its own TLAB (thread-local allocation buffer) by bumping a pointer. A TLAB refill and very large objects take a slower shared path.
13. Follow-up: how does padding against false sharing work if the JVM reorders fields?
HotSpot packs fields by size, so padding fields declared next to a hot field can move. Padding uses class inheritance (fields of a superclass come first) or @Contended with -XX:-RestrictContended.
14. List the JVM memory areas.
Per-thread stacks; shared heap, metaspace, and code cache; direct buffers and other native memory (GC structures, native libraries, thread structures). Process memory is the sum, so the heap is only part of RSS.
15. Map each failure to its cause.
| Error | Typical cause |
|---|---|
StackOverflowError |
deep recursion; -Xss default 1 MB on Linux x64 |
OutOfMemoryError: Java heap space |
live data exceeds -Xmx or a leak |
GC overhead limit exceeded |
almost all time goes to GC while little memory is recovered |
Metaspace |
class loader leak (metaspace is unlimited by default unless capped) |
Direct buffer memory |
direct buffers past MaxDirectMemorySize |
unable to create native thread |
OS thread or memory limit |
Requested array size exceeds VM limit |
array length near Integer.MAX_VALUE |
16. Scenario: a container is OOM-killed while the heap is at 60 percent. Where is the memory?
Outside the heap: thread stacks, metaspace, code cache, direct buffers, GC structures. Enable -XX:NativeMemoryTracking=summary and read jcmd <pid> VM.native_memory summary. Size the heap as a percentage of the container limit (MaxRAMPercentage, default 25) and leave room for non-heap memory.
17. What happens when the code cache fills?
The JIT stops compiling and the application slows down without an error. Size it with ReservedCodeCacheSize; at 240 MB or more it is segmented by tier.
Safepoints, warmup, and deoptimization
18. Why are the first requests slow?
Code runs interpreted, classes load and initialize, invokedynamic sites bootstrap, the JIT competes for CPU, and caches are cold. Warm up with realistic traffic before reporting ready; CDS, AppCDS, and the AOT cache (Java 24 JEP 483, extended in Java 25 by JEPs 514 and 515) cut startup work.
19. What is a safepoint, and what is time to safepoint?
A point where all Java threads stop so the JVM can do work that needs a consistent view, such as some GC phases and deoptimization. Polls sit at method returns and loop back-edges. Time to safepoint is the wait for the slowest thread to reach a poll; loop strip mining (JDK 10+) bounds it for counted loops. Handshakes (Java 10) stop one thread at a time.
20. What triggers deoptimization?
An uncommon trap in compiled code: a branch, null, or type the profile never saw. Also class loading that breaks a class hierarchy analysis assumption. The JVM falls back to the interpreter and recompiles later.
21. What is OSR?
On-stack replacement compiles a long-running loop while it executes and switches into it mid-loop. PrintCompilation marks it with % and @ bci. OSR code can be weaker than a normal compilation, which distorts naive benchmarks.
JIT compilation
22. Describe the tiers.
Tier 0 interpreter; tier 1 C1 without profiling (trivial methods); tiers 2 and 3 C1 with profiling; tier 4 C2 using the profile. The common path is 0, 3, 4.
23. What are the inlining limits?
MaxInlineSize 35 bytes for any call site, FreqInlineSize 325 bytes for hot sites on x64, MaxInlineLevel 15, and callees already compiled to more than InlineSmallCode (2,500 bytes) are not inlined. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining prints each decision and reason.
24. Why does inlining matter beyond saving a call?
It exposes the callee's code to the caller's optimizations: escape analysis, constant folding, range check elimination. Without inlining, an allocation passed to a callee escapes.
25. Monomorphic, bimorphic, megamorphic?
One or two receiver classes seen at a call site: C2 inlines behind type checks. Three or more: a virtual call, unless one class takes about 90 percent of calls. A megamorphic site blocks inlining and everything that depends on it.
26. Follow-up: a library loads a second implementation of an interface at run time and latency jumps. Why?
Class hierarchy analysis had bound calls to the only implementation. Loading another invalidates that dependency, compiled code is deoptimized, and the call sites may become bimorphic or megamorphic.
27. What is an intrinsic?
A JDK method the JIT replaces with hand-written machine code with identical results: System.arraycopy, Math.sqrt, Integer.bitCount, String.equals, and others.
28. What are the escape states, and what does each allow?
NoEscape: the object is scalar replaced (its fields become locals, no allocation). ArgEscape: allocated, but locks on it can be removed. GlobalEscape: allocated normally. Scalar replacement happens only in C2 code and depends on inlining.
29. Follow-up: an object escapes on a rare error path. Is it still scalar replaced on the fast path?
No. C2 does not do partial escape analysis: if the object escapes on any profiled path, it is allocated on every call. Arrays are scalar replaced only with constant indexes and at most 64 elements.
30. What loop optimizations does C2 apply?
For counted loops (an int index, a fixed bound): unrolling, range check elimination by loop predication, loop-invariant hoisting, and SuperWord auto-vectorization. Hoisting is the same transformation behind the non-volatile stop-flag bug.
31. How is generated code inspected?
PrintAssembly with the hsdis disassembler library, and JITWatch over -XX:+LogCompilation output. Read inlining logs first. The Vector API gives explicit SIMD; it is incubating in Java 25 and needs --add-modules jdk.incubator.vector.
Garbage collection
32. How does the GC decide what is live?
By tracing from GC roots: locals in active frames, static fields, JNI handles, and live threads. Unreachable cycles are garbage, which reference counting cannot collect without a cycle detector. Compiled code may treat a local as dead after its last use.
33. Mark-sweep, mark-compact, copying?
Mark-sweep cost grows with heap size and leaves fragmentation. Mark-compact removes fragmentation by moving objects and fixing references. Copying costs scale with live data and leaves one free block; it suits the young generation, where most objects die.
34. Explain the generational layout and tenuring.
Young is eden plus two survivor spaces, collected by copying. Objects age once per young GC they survive and are promoted at the tenuring threshold, at most MaxTenuringThreshold (15 by default, also the maximum because the age has 4 bits).
35. Minor, major, full?
Minor collects only young. Full is a stop-the-world collection of the whole heap. "Major" is used loosely for old-generation collections.
36. How does a young GC find old-to-young references without scanning the old generation?
A post-write barrier dirties a card (512 bytes of heap) in the card table on each reference store. The young GC scans only dirty cards.
37. Why does concurrent marking need a barrier?
In tri-color marking, an object is lost when a black object gains a reference to a white one and the last gray path to it is removed. SATB (G1, Shenandoah) records overwritten values, which prevents the second effect; incremental update (CMS) records new values, which handles the first.
38. When are soft, weak, and phantom references cleared?
Soft: before an OutOfMemoryError, under memory pressure. Weak: at the first GC that finds the referent weakly reachable. Phantom: enqueued after the referent is unreachable; get() always returns null. A ReferenceQueue delivers cleared references after a collection.
39. Show the collector beans of a JVM.
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
public class GcBeans {
public static void main(String[] args) {
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.println(gc.getName());
}
}
}Copy
MarkSweepCompactSerial reports Copy and MarkSweepCompact. Parallel reports PS Scavenge and PS MarkSweep; G1 reports G1 Young Generation, G1 Concurrent GC, and G1 Old Generation. Monitoring code that hardcodes one set of names breaks when the collector changes.
40. How does G1 organize the heap?
As equal regions (1 MB to 32 MB chosen by ergonomics for about 2048 regions; up to 512 MB set explicitly). Young and old are sets of regions. Young and mixed collections are stop-the-world evacuations of a collection set; mixed collections add old regions with the most garbage first.
41. What is a humongous object, and why does it matter?
An object strictly larger than half a region. It goes straight into contiguous old regions and wastes the tail of the last one. Many large arrays cause fragmentation and early marking; a larger region size or smaller arrays fix it.
42. What do remembered sets do in G1?
Each region tracks incoming cross-region references so it can be evacuated without scanning the heap. The post-write barrier enqueues dirty cards, and refinement threads update the remembered sets.
43. How does MaxGCPauseMillis work?
It is a goal (200 ms by default), not a guarantee. G1 predicts costs and sizes the young generation and the mixed collection set to meet it. A very low goal shrinks young, raising GC frequency and lowering throughput.
44. What starts a G1 marking cycle?
Old occupancy crossing the IHOP: 45 percent initially, adaptive by default (G1UseAdaptiveIHOP). The cycle has short Remark and Cleanup pauses; mixed collections follow.
45. Scenario: the G1 log shows "To-space exhausted" followed by a full GC.
Evacuation failure: there was no free region to copy into, so objects stayed in place, and the heap fell back to a stop-the-world full compaction. Causes: heap too small, or marking started too late. Fixes: more heap or G1ReservePercent, earlier marking (lower IHOP), lower allocation rate.
46. Follow-up: which MXBean counts what in G1?
"G1 Young Generation" counts young and mixed pauses, "G1 Concurrent GC" counts Remark and Cleanup, "G1 Old Generation" counts full GCs. A non-zero old-generation count means full GCs happened.
47. How do ZGC and Shenandoah keep pauses short?
They move objects concurrently with the application; G1 moves them in pauses. Pause time therefore does not grow with the live set.
48. How does ZGC relocate concurrently?
References carry color bits with GC metadata. A load barrier checks the color on each reference load and heals stale references in place. Relocation uses per-page forwarding tables; remapping happens lazily through barriers and the next marking. ZGC does not use compressed oops.
49. What is the state of generational ZGC?
Opt-in in Java 21, the default ZGC mode in Java 23, the only mode from Java 24. -XX:-ZGenerational is ignored with a warning on Java 25.
50. Scenario: ZGC reports sub-millisecond pauses, but p99.9 latency spikes during GC. Why?
Allocation stalls: threads wait when the heap runs out before a cycle finishes, and stalls are not counted as pauses. Check Allocation Stall log lines. Fix with headroom sized from allocation rate times cycle duration, or a lower allocation rate. Time to safepoint also still delays threads.
51. How does Shenandoah differ from ZGC?
It started with Brooks forwarding pointers and now uses load-reference barriers; it keeps compressed oops. When allocation outpaces it, it falls back to a degenerated GC, then a full GC. Its default mode is satb; generational mode is experimental in 24 and accepted without an unlock flag in this JDK 25 build.
52. Compare the collectors.
| Collector | Pauses | Throughput | Footprint | Typical use |
|---|---|---|---|---|
| Serial | long, single thread | fine for small heaps | smallest | small containers, 1 CPU |
| Parallel | long, many threads | highest | small | batch jobs |
| G1 | goal-based, default 200 ms | good | moderate | general services |
| ZGC, Shenandoah | sub-millisecond | lower | needs headroom | latency-sensitive services |
| Epsilon | none, never collects | no GC cost | heap must hold all garbage | benchmarks, garbage-free services |
53. Which collector does the JVM pick by default?
G1 on server-class machines; Serial on machines that are not server-class, such as a container with one CPU. Production sets the collector and -Xmx explicitly because defaults change with the machine.
54. What is the default heap size?
25 percent of available memory (MaxRAMPercentage), 50 percent on small machines (MinRAMPercentage). Container limits are detected by default (UseContainerSupport).
55. What is the tuning procedure?
State the goal (latency, throughput, footprint), measure with -Xlog:gc and allocation profiling, reduce the allocation rate first, then size the heap, then choose the collector, then change one flag at a time.
56. Scenario: a trading system must never pause during market hours. Options?
Pre-allocate and reuse objects so the steady state allocates nothing, verified with getThreadAllocatedBytes. Then either ZGC for very short pauses, or Epsilon with a heap large enough for a trading day and a planned restart. CMS is not an option: it was removed, and -XX:+UseConcMarkSweepGC is unrecognized on JDK 25.
Off-heap memory
57. What does this print?
import java.nio.ByteBuffer;
public class FlipCompact {
public static void main(String[] args) {
ByteBuffer b = ByteBuffer.allocate(8);
b.put((byte) 1).put((byte) 2).put((byte) 3);
b.flip();
System.out.println("pos " + b.position() + " limit " + b.limit());
b.get();
b.compact();
System.out.println("pos " + b.position() + " limit " + b.limit());
System.out.println("order " + b.order());
}
}pos 0 limit 3
pos 2 limit 8
order BIG_ENDIANflip sets limit to position and position to 0. After one read, compact moves the two unread bytes to the start and sets position after them for writing. A new buffer is big-endian on every platform; x86 is little-endian.
58. Heap or direct buffer?
Channel I/O with a heap buffer copies through a temporary direct buffer. Direct buffers avoid the copy but are limited by MaxDirectMemorySize (default: the max heap) and freed only after the buffer object is collected.
59. What does a memory-mapped file give, and what does force do?
Access through the page cache with no system call per read or write; the first touch of a page can fault. force writes dirty pages to storage for durability; other processes mapping the file already see changes.
60. Spot the bug: a service maps and deletes a temp file per request and disk usage grows.
A MappedByteBuffer has no public unmap; the mapping lasts until the buffer is collected, and on Linux deleting the file does not free its space while it is mapped. Use FFM: FileChannel.map(mode, offset, size, arena) unmaps when the arena closes.
61. What are the Arena kinds?
Confined (one thread, closed explicitly), shared (any thread, closed explicitly), auto (freed by the GC), global (never freed). Every segment access checks bounds, lifetime, and thread confinement: access after close throws IllegalStateException, from the wrong thread WrongThreadException.
62. How is FFM fast if every access is checked?
VarHandles stored in static final fields and built from layout paths compile to plain loads and stores, with checks hoisted or removed by the JIT.
63. Follow-up: what are the struct layout traps?
Layouts add no padding automatically; misaligned fields are rejected, so padding is written with paddingLayout. Downcalls and reinterpret are restricted methods; --enable-native-access removes the warning. invokeExact on a downcall handle needs exact static types.
Linux for Java
64. How does a Java thread map to Linux?
A platform thread is one kernel task; a process is a group of tasks sharing an address space. Virtual threads run on a few carrier tasks. /proc/<pid>/task/ lists the tasks.
65. Voluntary vs involuntary context switches?
Voluntary switches come from blocking (I/O, locks, sleep). Involuntary ones come from preemption. A rising involuntary count on a hot thread (nonvoluntary_ctxt_switches in /proc/thread-self/status) means core contention.
66. How is a thread pinned to a core?
The JDK has no affinity API. Use taskset or numactl at launch, or a native call, and reserve cores with isolcpus and nohz_full. availableProcessors() follows the affinity mask and container limits. Thread.setPriority has no effect on Linux with the default ThreadPriorityPolicy=0.
67. Which scheduler does Linux use?
EEVDF since kernel 6.6, which adds virtual deadlines; CFS before it, which runs the task with the least virtual runtime.
68. Spot the bug: writing a 1 MB file byte by byte takes seconds.
FileOutputStream.write(int) makes one system call per byte. Each syscall costs a mode switch; a BufferedOutputStream makes one per flush. strace -c counts syscalls but adds heavy overhead.
69. Minor vs major page faults?
Reserving virtual memory is free; physical pages are assigned on first touch. A minor fault is served from memory; a major fault needs disk I/O (a file page or swap). /proc/self/stat fields 10 and 12 count them.
70. What does -XX:+AlwaysPreTouch change?
It touches every heap page at startup, so page faults move from the first GCs and allocations to startup, and the full heap becomes resident at once. It is false by default.
71. Huge pages: what and how?
2 MiB pages cut page faults and TLB misses by 512 times compared with 4 KiB pages. THP in always mode can add compaction and khugepaged pauses, so the common setting is THP madvise plus -XX:+UseTransparentHugePages. UseLargePages uses pre-reserved hugetlbfs pages instead.
72. Scenario: GC pauses grow from 10 ms to seconds after a week.
Check VmSwap and majflt: swapped heap pages turn GC into disk I/O. Lower vm.swappiness, size the heap to fit memory, or disable swap for the host.
73. perf stat vs perf record?
perf stat counts hardware and software events (cycles, cache misses, faults). perf record samples stacks; JIT-compiled frames need a perf map to have names.
Performance tooling
74. Why does timing a loop with System.nanoTime mislead?
The code runs in several modes (interpreted, C1, C2, OSR) during the measurement, and the JIT may delete unused results (dead code elimination) or precompute constant inputs (constant folding).
75. How does JMH prevent those problems?
Warmup iterations whose results are discarded; results returned from @Benchmark or sunk in a Blackhole; inputs read from @State fields; @Fork runs each benchmark in a fresh JVM so profiles from other benchmarks do not leak in.
76. Follow-up: what is wrong with @Setup(Level.Invocation) on a 50 ns benchmark?
It runs setup and timer calls around every invocation, and that overhead is larger than the measured work. It is meant only for long operations.
77. What does -prof gc report?
gc.alloc.rate.norm, bytes allocated per operation, which is often more stable than time and shows whether escape analysis worked. In the results, the Error column is a confidence interval; overlapping intervals show no difference.
78. Why is JFR safe in production?
It is built into HotSpot and writes events into thread-local and global ring buffers with low overhead. Thresholds drop short events, which keeps lock and I/O events cheap. Start it with -XX:StartFlightRecording or jcmd JFR.start; read dumps with RecordingFile or live with RecordingStream.
79. How is a custom JFR event written?
Extend jdk.jfr.Event, describe it with @Name, @Label, @Category, and @StackTrace, then call begin, end, shouldCommit, and commit. It records only when enabled by name.
80. What is safepoint bias?
Profilers that sample at safepoints attribute time to poll locations, not to the hot code. async-profiler (signals and perf_events, including native and kernel frames) and JFR's execution sampler avoid it.
81. How is a flame graph read?
Width is the share of samples; the x-axis is not time. The top edge is where the cost is, and wide plateaus at the top are hot code. Mode decides the meaning: cpu is on-CPU time, wall includes blocked time, alloc is bytes, lock is contention.
82. Why do averages mislead, and what is reported instead?
Averages hide tails. Report p50, p99, p99.9, and max, with the offered load. Percentiles cannot be averaged across hosts or intervals; histograms can be merged.
83. Show why averaging percentiles is wrong.
import java.util.Arrays;
public class MergePercentiles {
static long p99(long[] values) {
long[] s = values.clone();
Arrays.sort(s);
int idx = (int) Math.ceil(0.99 * s.length) - 1;
return s[idx];
}
public static void main(String[] args) {
long[] hostA = new long[100];
long[] hostB = new long[100];
Arrays.fill(hostA, 1);
Arrays.fill(hostB, 1);
for (int i = 0; i < 5; i++) hostB[i] = 500;
long[] all = new long[200];
System.arraycopy(hostA, 0, all, 0, 100);
System.arraycopy(hostB, 0, all, 100, 100);
System.out.println("p99 A " + p99(hostA) + ", p99 B " + p99(hostB));
System.out.println("average of p99s " + (p99(hostA) + p99(hostB)) / 2);
System.out.println("p99 of merged data " + p99(all));
}
}p99 A 1, p99 B 500
average of p99s 250
p99 of merged data 500The merged p99 is the true value; the average of the per-host values understates it. HdrHistogram merges histograms with fixed precision and no allocation per record.
84. What is coordinated omission?
A closed-loop load tester waits for each response before sending the next request. During a stall it sends nothing, so the requests that would have waited are never measured and the tail looks small.
85. How is coordinated omission corrected?
Use an open-loop schedule and measure latency from the intended start time, not the actual send time. When the planned rate is known, HdrHistogram's recordValueWithExpectedInterval or copyCorrectedForCoordinatedOmission adds the missing samples.
86. Scenario: p99.9 shows rare 30 ms spikes. Where are they from?
In the JVM: GC pauses and allocation stalls, safepoints and time to safepoint, JIT deoptimization and recompilation, page faults, involuntary context switches, and queueing behind a slow request. Correlate the spike times with the GC log, -Xlog:safepoint, JFR, and /proc fault and context switch counters.
Interview points
- Class identity is name plus loader; constants do not trigger initialization; a failed
<clinit>becomesNoClassDefFoundError. - Object header 12 bytes with compressed class pointers; compressed oops end near 32 GB.
- Non-heap memory (stacks, metaspace, code cache, direct buffers) explains container OOM kills.
- Inlining enables escape analysis; megamorphic sites and partial escape block it.
- G1 meets a pause goal by sizing collections; ZGC and Shenandoah move objects concurrently and fail by stalling.
- Pre-touch, huge pages, no swap, and pinned cores remove OS-level jitter.
- Benchmarks need JMH; latency needs histograms and open-loop load.
Summary
- 86 questions cover every lesson in sections 11 to 16.
- Runnable answers show class initialization triggers, failed initialization, collector bean names,
ByteBufferstate changes, and why percentiles cannot be averaged. - Language and collections questions are in Core Java questions; concurrency and the memory model in Concurrency and JMM questions.
