Java and the JVM

Java's runtime behaviour depends on the JVM, its compiler and its garbage collector. These notes describe HotSpot; details vary by JDK version.

Tiered compilation

HotSpot profiles running code and compiles frequently used methods. It can inline calls and remove allocations when their effects are not observable.

Optimisation may rely on assumptions about the types seen at a call site. If an assumption stops holding, the JVM can deoptimise and recompile the code. Benchmark warmup and realistic inputs matter.

Collectors

Collectors balance throughput, memory use and pause times. G1, ZGC and Shenandoah make different tradeoffs; their availability and behaviour depend on your JDK.

Allocation is often cheap, but keeping objects alive increases collection work. Use try-with-resources for resources that need prompt release, such as files and sockets.

The memory model

Synchronisation establishes visibility and ordering between threads. volatile supports visibility and ordering for a field, but does not make a compound operation such as an increment atomic.

Properly constructed final fields have additional visibility guarantees. Avoid exposing this before construction finishes. See Concurrency.

Where the surprises are

  • Compare boxed values by value rather than with ==.
  • Generics are mostly erased at runtime; arrays retain their element type.
  • Clear request-specific ThreadLocal values when using pooled threads.
  • Virtual threads help with blocking workloads. Check your JDK's limitations rather than assuming they speed up CPU-bound work.

Measurement

Use JMH for microbenchmarks. It handles warmup and helps avoid measuring dead-code elimination or constant folding.

JFR records runtime events, including allocation, collection and contention. async-profiler provides CPU and other profiles.

Before the runtime details

Check java -version and javac -version; running and compiling with different JDKs can produce confusing failures. Use the version expected by your course or project. A Maven or Gradle wrapper helps keep the build tool consistent.

Compilation produces bytecode. The runtime needs the correct classpath or module path to locate application code and dependencies. A missing class and an incompatible class version are different problems.

Objects and collections

Choose collections by the operations you need. Lists preserve a sequence, sets represent membership, and maps associate keys with values. Hash-based collections depend on consistent equals and hashCode implementations.

Avoid mutating fields used to identify a key while that key is stored in a hash map. An unmodifiable collection also does not make its elements immutable.

Exceptions and boundaries

Catch an exception where you can recover or add useful context. Keep the original cause when translating an exception. Logging and rethrowing at every layer often produces several copies of the same failure.

Close files, sockets and database resources promptly. Garbage collection manages memory; it does not replace resource ownership.

Reading