What are the young, old and Metaspace areas in the Java heap?
The young generation holds newly allocated objects. It is split into two spaces: the Eden, where almost every object is born, and two survivor spaces, which hold objects that have survived at least one collection. The old generation holds objects that have survived many collections and are assumed to be long-lived — the classic examples are caches, connection pools and anything held in a static field.
Metaspace is not part of the Java heap. It holds the class metadata: the loaded class objects, their constant pools, their method and field definitions and their bytecode. It replaced the permanent generation, which used to live inside the heap but was never collected. The practical consequence is that Metaspace is native memory, sized by the operating system rather than by your heap setting, and it is bounded by the machine rather than by a flag you set for the heap. So a class-loading leak shows up as native memory growth rather than as a Java heap problem.
How objects move between the areas is the part worth understanding. When the young generation fills up, a minor collection runs. Objects that are still alive are copied from Eden into a survivor space, and the survivor spaces swap roles. Objects that survive enough minor collections get promoted to the old generation. A major collection then collects the old generation, and it is far more expensive because it has to trace a much larger object graph, often with the young generation collected at the same time.
Here is the same thing as runnable code:
public class Main {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println("max heap MB : " + runtime.maxMemory() / (1024 * 1024));
System.out.println("total heap MB : " + runtime.totalMemory() / (1024 * 1024));
System.out.println("free heap MB : " + runtime.freeMemory() / (1024 * 1024));
System.out.println("used heap MB : " + usedMb(runtime));
// Allocate a little and watch the numbers move.
java.util.List<byte[]> ballast = new java.util.ArrayList<>();
for (int i = 0; i < 30; i++) {
ballast.add(new byte[1024 * 1024]);
}
System.out.println("after 30MB : used " + usedMb(runtime) + " MB");
System.out.println("ballast size : " + ballast.size());
}
static long usedMb(Runtime runtime) {
return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}
}