← Back to Programming

The JVM, memory, and the Java ecosystem

Knowing how the JVM manages memory, and how Java projects are built and organised, helps you write efficient programs and work on real codebases.

Stack and heap

Each thread has a stack holding method calls and local variables; objects live on the shared heap. When a method creates new Circle(), the object goes on the heap and a reference sits in the method's stack frame. Very deep recursion can exhaust the stack, causing StackOverflowError.

Garbage collection

Java frees memory automatically. The garbage collector finds objects no longer reachable from any live reference and reclaims their space. Programmers do not call free, which eliminates whole classes of bugs, though holding references unnecessarily, such as in an ever-growing list, can still cause memory leaks and OutOfMemoryError.

Just-in-time compilation

The JVM starts by interpreting bytecode, then profiles the program and compiles frequently run 'hot' methods to optimised machine code with its JIT compiler. This is why Java programs often speed up after warming up, and why long-running servers can be very fast.

Packages and modules

Packages group related classes and prevent name clashes, such as com.example.billing. Package names usually reverse a domain name. import brings classes into scope: import java.util.List;. Java 9 added modules, which declare which packages a library exports and which other modules it needs.

Build tools and dependencies

Real projects use build tools such as Maven and Gradle. They compile code, run tests, package applications into JAR files, and download libraries (dependencies) from repositories such as Maven Central. A pom.xml or build.gradle file declares the project's dependencies and versions.

Testing with JUnit

JUnit is the standard Java testing framework. A test method is annotated @Test and uses assertions: assertEquals(12, calc.area(3, 4)); assertThrows(IllegalArgumentException.class, () -> calc.area(-1, 2));. Build tools run all tests automatically, and failing tests stop a broken build.

Java versions and uses

Java releases a new version every six months, with long-term support (LTS) versions every two years, such as Java 17, 21, and 25. Java powers enterprise back-ends with frameworks such as Spring, big-data tools such as Apache Kafka, and Android apps, alongside Kotlin, a JVM language Google prefers for Android.

Worked example

A recursive method with no base case, int f(int n) { return f(n + 1); }, keeps adding stack frames until the JVM throws StackOverflowError, typically after thousands of calls depending on stack size. Adding a base case, if (n > 100) return n;, fixes it. Large loops creating short-lived objects, by contrast, are handled efficiently by the garbage collector.

Key takeaways

Local variables and calls live on each thread's stack; objects live on the heap. Garbage collection reclaims unreachable objects, but lingering references still leak memory. The JIT compiler optimises hot code at run time. Packages organise classes; modules control exports. Maven and Gradle build projects and manage dependencies. JUnit tests use @Test and assertions. Java releases every six months with LTS versions every two years, powering servers, big data, and Android.

Flashcard

Stack

Per-thread storage for calls and locals

StackOverflowError.

Check your understanding

A Java program keeps adding objects to a static list that is never cleared and eventually crashes with OutOfMemoryError. Why didn't garbage collection prevent this?

← Previous lesson Next lesson →