The JIT code cache: ReservedCodeCacheSize and 'CodeCache is full'

HotSpot does not run hot Java methods from bytecode for long: the JIT compilers turn them into machine code and store it in a dedicated native memory area called the code cache. When that area fills up, the JVM stops compiling, and an application can slowly fall back to interpreted speed without any exception being thrown.
What lives in the code cache
The code cache holds the output of the JIT compilers (C1 and C2 in a standard HotSpot build), plus code the JVM generates for itself: the interpreter, stubs and adapters between interpreted and compiled code. With tiered compilation, which is the default, a method is often compiled more than once: first by C1 with profiling code, later by C2 with optimisations based on that profile. Both versions occupy space for a while.
The cache is reserved at startup as one contiguous range of virtual memory and committed as it grows. Its maximum size is fixed for the life of the process.
Default sizes
The java launcher reference gives the numbers for -XX:ReservedCodeCacheSize:
| Configuration | Default maximum |
|---|---|
| Tiered compilation (default) | 240 MB |
-XX:-TieredCompilation | 48 MB |
| Upper limit of the option | 2 GB (larger values are rejected) |
The same reference documents -XX:InitialCodeCacheSize, whose default depends on the platform, and warns that the maximum must not be smaller than the initial size.
The segmented code cache
JEP 197, delivered in JDK 9, split the cache into separate code heaps. The launcher reference says that with -XX:+SegmentedCodeCache there are separate segments for non-method code, profiled methods and non-profiled methods, that these segments are not resized at runtime, and that the feature is on by default when tiered compilation is enabled and ReservedCodeCacheSize is at least 240 MB.
| Segment | Contents | Size option |
|---|---|---|
| non-nmethods | JVM internal code: interpreter, stubs, adapters | -XX:NonNMethodCodeHeapSize |
| profiled nmethods | C1 code with profiling, short-lived | -XX:ProfiledCodeHeapSize |
| non-profiled nmethods | Fully optimised C2 code (and C1 code without profiling) | -XX:NonProfiledCodeHeapSize |
Because the segments do not borrow from each other, one segment can fill while the others still have room. Monitoring the total is therefore not enough; watch each segment.
Symptoms of a full code cache
When there is no space left for new compiled code, HotSpot prints a warning on the console or in the JVM output, typically:
OpenJDK 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.
OpenJDK 64-Bit Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize=
Nothing is thrown to application code. What you see instead is indirect:
- CPU use rises and throughput falls hours or days after startup, without a change in traffic;
- newly exercised code paths (a rarely used endpoint, a monthly batch) are dramatically slower than in tests;
- the warning sits in a log file nobody reads, because it went to standard output.
Since the warning is the only explicit signal, make sure the JVM's own output is captured by your log pipeline.
Monitoring
jcmd
$ jcmd <pid> Compiler.codecache
This prints each code heap with its size, used space, the high-water mark (max_used) and free space, followed by counts of compiled methods and whether compilation is still enabled. A max_used close to size in any segment is your early warning. jcmd <pid> Compiler.codelist lists the compiled methods themselves, and jcmd <pid> help shows which compiler commands your release supports.
Memory pool MXBeans
Each code heap is exposed as a MemoryPoolMXBean. With a segmented cache the pool names are CodeHeap 'non-nmethods', CodeHeap 'profiled nmethods' and CodeHeap 'non-profiled nmethods'; with a single segment (JDK 8, or segmentation disabled) there is one pool called Code Cache. Most metrics agents that export JVM memory pools pick these up automatically.
for (MemoryPoolMXBean p : ManagementFactory.getMemoryPoolMXBeans()) {
if (p.getName().startsWith("CodeHeap") || p.getName().equals("Code Cache")) {
MemoryUsage u = p.getUsage();
System.out.printf("%s used=%d max=%d%n", p.getName(), u.getUsed(), u.getMax());
}
}
At exit and in Flight Recorder
-XX:+PrintCodeCache prints a summary when the JVM exits, which is handy in load tests. JDK Flight Recorder records code cache statistics and a jdk.CodeCacheFull event; see the JFR guide.
When to raise ReservedCodeCacheSize
Raise it when the measurements show a segment near its limit in steady state, or when you see the warning. Typical cases are very large applications: many frameworks, generated code, scripting engines, or many dynamically generated classes that are compiled. A doubling is a reasonable first step; measure again under realistic traffic.
# a large application server: give the JIT more room
$ java -XX:ReservedCodeCacheSize=512m -jar app.jar
Points to keep in mind:
- It costs native memory. Reserved space is mostly virtual until used, but used code cache counts against a container limit just like heap. Include it in the budget described in the container guide.
- Below 240 MB, segmentation turns off by default (per the rule above). Shrinking the cache to save memory changes its layout too.
- Do not disable tiered compilation to "save" code cache without measuring: it changes warm-up behaviour and the default size at the same time.
- Segment sizes are rarely worth tuning by hand. If you set them, they must add up consistently with
ReservedCodeCacheSize; start with the total and let the JVM split it.
Code cache flushing
When space runs short, HotSpot can evict compiled methods to make room before giving up on compilation. The launcher reference describes -XX:+UseCodeCacheFlushing, enabled by default, as flushing the code cache before shutting down the compiler. Flushing helps a cache that is full of cold code; it does not help an application whose hot code genuinely does not fit. If your graphs show the cache filling, being flushed, refilling and filling again, with compiler threads busy long after startup, the cache is too small.
Native Memory Tracking view
With -XX:NativeMemoryTracking=summary, the Code category of jcmd <pid> VM.native_memory summary shows reserved and committed code cache memory alongside every other JVM subsystem. It is the quickest way to see the code cache in the context of total process size. See the NMT guide.
Checklist
- Search JVM output for
CodeCache is full. - Run
jcmd <pid> Compiler.codecacheon a long-running instance and comparemax_usedtosizeper segment. - Export the code heap memory pools to your metrics.
- Raise
-XX:ReservedCodeCacheSizein steps, within the 2 GB ceiling and your memory budget.
Sources
- The java command, JDK 21 reference (ReservedCodeCacheSize, InitialCodeCacheSize, SegmentedCodeCache)
- JEP 197: Segmented Code Cache
- The jcmd command
- MemoryPoolMXBean javadoc
MemoryNative Memory Tracking
Heap is only part of the process: read the NMT summary, diff against a baseline, and know what it cannot see.
MemoryMetaspace and class loader leaks
Class metadata outside the heap: the flags, the jcmd views, and how redeploys leak class loaders.
ProductionFlight Recorder and Mission Control
Continuous recording, on-demand dumps with jcmd, the events that matter, and reading .jfr files.