<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ghassane LATFI</title>
    <description>The latest articles on DEV Community by Ghassane LATFI (@glatfi).</description>
    <link>https://dev.to/glatfi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082153%2F3365367c-bf18-470e-a1c3-becc1ac71ea2.png</url>
      <title>DEV Community: Ghassane LATFI</title>
      <link>https://dev.to/glatfi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/glatfi"/>
    <language>en</language>
    <item>
      <title>The 6 JVM OutOfMemoryErrors and how to read each one</title>
      <dc:creator>Ghassane LATFI</dc:creator>
      <pubDate>Sun, 06 Sep 2026 18:10:58 +0000</pubDate>
      <link>https://dev.to/glatfi/the-6-jvm-outofmemoryerrors-and-how-to-read-each-one-1hc4</link>
      <guid>https://dev.to/glatfi/the-6-jvm-outofmemoryerrors-and-how-to-read-each-one-1hc4</guid>
      <description>&lt;p&gt;&lt;code&gt;java.lang.OutOfMemoryError&lt;/code&gt; is not a problem, it's a family of problems. What matters is the text after the colon. &lt;code&gt;Java heap space&lt;/code&gt; and &lt;code&gt;unable to create native thread&lt;/code&gt; don't share a cause, aren't seen with the same tool, and aren't fixed in the same place. Raising &lt;code&gt;-Xmx&lt;/code&gt; solves the first and does nothing for the second.&lt;/p&gt;

&lt;p&gt;This article goes through the six messages HotSpot produces in practice. For each one: what the JVM was trying to do, why it couldn't, and where to start. Every excerpt comes from Temurin 25.0.4 on Linux, inside a container, using programs of a few lines that trigger the error on purpose.&lt;/p&gt;

&lt;p&gt;The starting point is a simple reminder. The JVM has several memory areas, each with its own limit. The heap for objects, Metaspace for classes, direct memory for NIO buffers, one stack per thread. Each &lt;code&gt;OutOfMemoryError&lt;/code&gt; message names one of those areas. Once you know which one, the diagnosis is already half done. If what lives outside the heap isn't clear to you, &lt;a href="https://lajvm.fr/en/jvm-off-heap-memory/" rel="noopener noreferrer"&gt;Off-heap memory: where the JVM's RAM goes&lt;/a&gt; spells it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Java heap space
&lt;/h2&gt;

&lt;p&gt;The famous one. The JVM wanted to allocate an object in the heap, ran a full collection to make room, and still didn't have enough room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Java&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nc"&gt;HeapSpace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HeapSpace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program behind it is a few lines long, started with &lt;code&gt;-Xmx64m&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HeapSpace&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;retained&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;retained&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes the error readable isn't the stack trace, it's the GC log right before it. With &lt;code&gt;-Xlog:gc&lt;/code&gt;, you can watch the JVM struggle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[0.038s][info][gc] GC(7) Pause Young (Normal) (G1 Humongous Allocation) 63M-&amp;gt;&lt;/span&gt;63M&lt;span class="o"&gt;(&lt;/span&gt;64M&lt;span class="o"&gt;)&lt;/span&gt; 0.461ms
&lt;span class="gp"&gt;[0.040s][info][gc] GC(8) Pause Full (G1 Compaction Pause) 63M-&amp;gt;&lt;/span&gt;63M&lt;span class="o"&gt;(&lt;/span&gt;64M&lt;span class="o"&gt;)&lt;/span&gt; 1.693ms
&lt;span class="gp"&gt;[0.042s][info][gc] GC(9) Pause Full (G1 Compaction Pause) 63M-&amp;gt;&lt;/span&gt;63M&lt;span class="o"&gt;(&lt;/span&gt;64M&lt;span class="o"&gt;)&lt;/span&gt; 1.975ms
&lt;span class="go"&gt;java.lang.OutOfMemoryError: Java heap space
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;63M-&amp;gt;63M&lt;/code&gt; on a Full GC is the signature. The collection ran and freed nothing: everything in the heap is still referenced. From there, only two cases. Either the heap is too small for the real load, and you can tell because the error shows up at peak traffic and then goes away. Or one object is holding on to everything else, the heap climbs week after week, and it's a leak.&lt;/p&gt;

&lt;p&gt;The reflex to have before you even start looking is to set two flags in production, once and for all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;-XX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JVM then writes a heap dump at the exact moment of the error, while the guilty object is still there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;java.lang.OutOfMemoryError: Java heap space
Dumping heap to /tmp/heap.hprof ...
Heap dump file created [36297923 bytes in 0.051 secs]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Analysing the dump is a topic of its own: &lt;a href="https://lajvm.fr/en/diagnosing-a-memory-leak-with-a-heap-dump/" rel="noopener noreferrer"&gt;Diagnosing a memory leak with a heap dump&lt;/a&gt;. And to tell a spike from a leak by reading the logs without any tool, &lt;a href="https://lajvm.fr/en/reading-gc-logs/" rel="noopener noreferrer"&gt;Reading GC logs without an external tool&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. GC overhead limit exceeded
&lt;/h2&gt;

&lt;p&gt;This one is surprising, because the heap isn't quite full. The JVM decided to give up early: it's spending more than 98% of its time collecting and recovering less than 2% of the heap each time. Going on would make no sense, so it throws:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[0.589s][info][gc] GC(111) Pause Full (Allocation Failure) 59M-&amp;gt;&lt;/span&gt;59M&lt;span class="o"&gt;(&lt;/span&gt;62M&lt;span class="o"&gt;)&lt;/span&gt; 7.321ms
&lt;span class="gp"&gt;[0.597s][info][gc] GC(112) Pause Full (Allocation Failure) 59M-&amp;gt;&lt;/span&gt;59M&lt;span class="o"&gt;(&lt;/span&gt;62M&lt;span class="o"&gt;)&lt;/span&gt; 7.980ms
&lt;span class="gp"&gt;[0.608s][info][gc] GC(113) Pause Full (Allocation Failure) 59M-&amp;gt;&lt;/span&gt;59M&lt;span class="o"&gt;(&lt;/span&gt;62M&lt;span class="o"&gt;)&lt;/span&gt; 10.785ms
&lt;span class="go"&gt;Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at GcOverhead.main(GcOverhead.java:16)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program, started with &lt;code&gt;-XX:+UseParallelGC -Xmx64m&lt;/code&gt; and &lt;code&gt;4096&lt;/code&gt; as argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GcOverhead&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Garbage size taken from the command line: with a constant,&lt;/span&gt;
        &lt;span class="c1"&gt;// the JIT removes the dead allocation and the demo ends in Java heap space.&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;garbage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// A small object kept forever, next to a bigger one dropped at&lt;/span&gt;
            &lt;span class="c1"&gt;// once: the heap fills slowly and each collection frees only&lt;/span&gt;
            &lt;span class="c1"&gt;// the garbage of the last iteration.&lt;/span&gt;
            &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
            &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;garbage&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It keeps one small object per iteration in a &lt;code&gt;HashMap&lt;/code&gt;, and allocates a 4 KB array next to it that's dropped at once. The heap fills slowly, and each Full GC only recovers the garbage of the last iteration. Here, over a hundred Full GCs of 7 to 11 ms, a few milliseconds apart, without the heap ever going down in the log. The JVM eventually calls it.&lt;/p&gt;

&lt;p&gt;A detail few people know: this message only exists with the &lt;strong&gt;Parallel GC&lt;/strong&gt;. The same program, with the same 64 MB, under G1 and under Serial gives a regular &lt;code&gt;Java heap space&lt;/code&gt;. I checked all three side by side. Since G1 has been the default collector since Java 9, this message is getting rarer, except on batch applications that kept &lt;code&gt;-XX:+UseParallelGC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Underneath, it's the same thing as case 1, only slower. Same diagnosis, same tools, same heap dump. The &lt;code&gt;-XX:-UseGCOverheadLimit&lt;/code&gt; flag silences the message, but gains nothing. With the flag, the same program went through 173 Full GCs instead of 113 before ending in &lt;code&gt;Java heap space&lt;/code&gt;. Don't use it: it delays the same error, with a frozen application in the meantime.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Metaspace
&lt;/h2&gt;

&lt;p&gt;Here the objects are fine. It's the classes that no longer fit. Metaspace stores the metadata of every loaded class, and it grows when classes get loaded and never unloaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.118&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;][&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;][&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;GC&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Pause&lt;/span&gt; &lt;span class="nf"&gt;Full&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Metadata&lt;/span&gt; &lt;span class="no"&gt;GC&lt;/span&gt; &lt;span class="nc"&gt;Clear&lt;/span&gt; &lt;span class="nc"&gt;Soft&lt;/span&gt; &lt;span class="nc"&gt;References&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;5.106&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Metaspace&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ClassLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defineClass1&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Native&lt;/span&gt; &lt;span class="nc"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ClassLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defineClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClassLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;962&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="n"&gt;$Loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log line says it all: the JVM attempted a special collection, &lt;code&gt;Metadata GC Clear Soft References&lt;/code&gt;, to unload classes, and couldn't unload any. The program, started with &lt;code&gt;-XX:MaxMetaspaceSize=32m&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.InputStream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Loader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;Loader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defineClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Meta$Payload"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payload&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResourceAsStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Meta$Payload.class"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAllBytes&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// A new loader each time: the class is defined again and again,&lt;/span&gt;
            &lt;span class="c1"&gt;// and the list keeps every loader alive.&lt;/span&gt;
            &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Loader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a fresh &lt;code&gt;ClassLoader&lt;/code&gt; on every iteration, defines the same class with it again, and keeps every loader in a list. As long as a loader is referenced, so are its classes.&lt;/p&gt;

&lt;p&gt;In real life the cause is almost always one of these three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot redeployments&lt;/strong&gt; on an application server, where the old version stays pinned by a thread or a &lt;code&gt;ThreadLocal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime class generation&lt;/strong&gt;: proxies, serialized lambdas, scripting engines, dynamic mapping. A library that generates one class per request instead of one per type.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;cache of &lt;code&gt;ClassLoader&lt;/code&gt;&lt;/strong&gt; or &lt;code&gt;Class&lt;/code&gt; objects growing without bound.
One detail changes everything in a container. Metaspace has &lt;strong&gt;no limit by default&lt;/strong&gt;. Without &lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt;, it never throws this error: it pushes the RSS up to the container limit, and the pod dies &lt;code&gt;OOMKilled&lt;/code&gt; without a word. Setting a reasonable limit fixes nothing, but it turns a silent death into an error with a stack trace:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;-XX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;MaxMetaspaceSize=256m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the diagnosis, a heap dump works too: &lt;code&gt;ClassLoader&lt;/code&gt; instances are objects, and their count in the dump gives the leak away. &lt;code&gt;jcmd &amp;lt;pid&amp;gt; VM.classloader_stats&lt;/code&gt; gives the same information live, without a dump.&lt;/p&gt;

&lt;p&gt;There's a sibling: &lt;code&gt;OutOfMemoryError: Compressed class space&lt;/code&gt;. That's the area, reserved next to Metaspace, holding the part of the metadata addressed through compressed pointers. Its size is fixed at startup, 1 GB by default, adjustable with &lt;code&gt;-XX:CompressedClassSpaceSize&lt;/code&gt;. Same cause, same cure as Metaspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cannot reserve N bytes of direct buffer memory
&lt;/h2&gt;

&lt;p&gt;NIO's direct buffers live outside the heap, in an area with its own limit. When it's full, the message has the merit of giving the numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Cannot&lt;/span&gt; &lt;span class="n"&gt;reserve&lt;/span&gt; &lt;span class="mi"&gt;1048576&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;direct&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;allocated:&lt;/span&gt; &lt;span class="mi"&gt;67108864&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;limit:&lt;/span&gt; &lt;span class="mi"&gt;67108864&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Bits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reserveMemory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;178&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DirectByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;DirectByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allocateDirect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;367&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nc"&gt;Direct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Direct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program, started with &lt;code&gt;-XX:MaxDirectMemorySize=64m&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Direct&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;buffers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allocateDirect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Java 11, the same message just says &lt;code&gt;Direct buffer memory&lt;/code&gt;, with no numbers. Since Java 17, you read straight away how much is allocated and what the limit is. That limit is set with &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt;, and by default it equals the maximum heap size. On an application with &lt;code&gt;-Xmx4g&lt;/code&gt;, there are potentially 4 GB of direct buffers next to the 4 GB of heap. In a container, that deserves an explicit value.&lt;/p&gt;

&lt;p&gt;The trap with this message is that it often shows up when direct memory isn't really in use. A &lt;code&gt;DirectByteBuffer&lt;/code&gt; gives its native memory back when the Java object is collected by the GC. With a large heap and rare collections, dead buffers pile up waiting for a collection that never comes. The JVM does attempt a &lt;code&gt;System.gc()&lt;/code&gt; before throwing, but if you've set &lt;code&gt;-XX:+DisableExplicitGC&lt;/code&gt;, that call does nothing, and the error lands with a half-empty heap. I checked: a program that allocates 2,000 buffers of 1 MB without keeping them, with a 64 MB limit, passes without the flag and fails with it.&lt;/p&gt;

&lt;p&gt;Two leads for the diagnosis. The &lt;code&gt;BufferPoolMXBean&lt;/code&gt; named &lt;code&gt;direct&lt;/code&gt;, exposed through JMX and by most metrics agents, gives the usage curve. And if Netty is in the application, its own allocator has its own metrics and its own leaks, usually a &lt;code&gt;ByteBuf&lt;/code&gt; that's never released. The details of the off-heap areas are in &lt;a href="https://lajvm.fr/en/jvm-off-heap-memory/" rel="noopener noreferrer"&gt;Off-heap memory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One last point, and it matters for what follows: this error is thrown by Java code, in &lt;code&gt;Bits.reserveMemory&lt;/code&gt;, not by the virtual machine itself. We'll see below why that changes how &lt;code&gt;-XX:+ExitOnOutOfMemoryError&lt;/code&gt; behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Unable to create native thread
&lt;/h2&gt;

&lt;p&gt;The JVM asked the operating system for a thread, and the system refused. The full message is cautious, because the JVM doesn't know why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[0.059s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 512k, guardsize: 0k, detached.
[0.060s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-279"
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
    at java.base/java.lang.Thread.start0(Native Method)
    at java.base/java.lang.Thread.start(Thread.java:1417)
    at Threads.main(Threads.java:8)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not a lack of memory, despite the class name. The program creates sleeping threads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Threads&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDaemon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" threads"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs with &lt;code&gt;-Xss512k&lt;/code&gt;, in a container started with &lt;code&gt;--pids-limit=300&lt;/code&gt;. The refused thread is called &lt;code&gt;Thread-279&lt;/code&gt;, the application's 280th. With the JVM's internal threads, that reaches the limit of 300, and &lt;code&gt;pthread_create&lt;/code&gt; returns &lt;code&gt;EAGAIN&lt;/code&gt;. The warning line just above is more useful than the exception: it gives the error code and the requested stack size.&lt;/p&gt;

&lt;p&gt;The causes, by frequency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;cgroup process limit&lt;/strong&gt;. On Kubernetes that's &lt;code&gt;pids.max&lt;/code&gt;, often set by the runtime without anyone knowing. A thread counts as a process.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;ulimit -u&lt;/code&gt;&lt;/strong&gt; of the user running the JVM.&lt;/li&gt;
&lt;li&gt;A real lack of memory, when each 1 MB stack eventually finds no room in the address space or in the container limit.
In every case the question is the same: why so many threads? A thread dump answers it, with the name and stack of each one. A pool created per request and never shut down is spotted in ten seconds: &lt;a href="https://lajvm.fr/en/reading-a-thread-dump-with-jstack/" rel="noopener noreferrer"&gt;Reading a thread dump with jstack&lt;/a&gt;. And if the application really needs thousands of waiting threads, virtual threads don't each get a native thread: &lt;a href="https://lajvm.fr/en/virtual-threads-in-production/" rel="noopener noreferrer"&gt;Virtual threads in production&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Requested array size exceeds VM limit
&lt;/h2&gt;

&lt;p&gt;The only one of the six that doesn't depend on available memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Requested&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="n"&gt;exceeds&lt;/span&gt; &lt;span class="no"&gt;VM&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nc"&gt;ArraySize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ArraySize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArraySize&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JVM refuses before even looking at the heap: I ran it with &lt;code&gt;-Xmx64m&lt;/code&gt; and with &lt;code&gt;-Xmx8g&lt;/code&gt;, same error. And the limit is very narrow. Only the last two lengths, &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt; and &lt;code&gt;Integer.MAX_VALUE - 1&lt;/code&gt;, are refused, for an &lt;code&gt;int[]&lt;/code&gt; as well as a &lt;code&gt;byte[]&lt;/code&gt;. An array of &lt;code&gt;Integer.MAX_VALUE - 2&lt;/code&gt; passes the check, and then the heap decides, with a &lt;code&gt;Java heap space&lt;/code&gt; if it's too small.&lt;/p&gt;

&lt;p&gt;In production you get there with &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt; used to mean "unlimited": &lt;code&gt;new StringBuilder(Integer.MAX_VALUE)&lt;/code&gt;, &lt;code&gt;ByteBuffer.allocate(Integer.MAX_VALUE)&lt;/code&gt;, or a computed size that lands exactly on it. The first two produce this exact message. On Java 8, &lt;code&gt;ArrayList&lt;/code&gt; also asked for an array of &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt; elements as a last resort while growing, and hit it.&lt;/p&gt;

&lt;p&gt;Collections on recent JDKs stop earlier. &lt;code&gt;ArrayList&lt;/code&gt;, &lt;code&gt;StringBuilder&lt;/code&gt; and &lt;code&gt;ByteArrayOutputStream&lt;/code&gt; cap their growth at &lt;code&gt;Integer.MAX_VALUE - 8&lt;/code&gt;, and when even that isn't enough, the error comes from Java code, with a different wording:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Required&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="mi"&gt;2147483631&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;too&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ArraysSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hugeLength&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ArraysSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;914&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ArraysSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newLength&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ArraysSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;907&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AbstractStringBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newCapacity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AbstractStringBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;344&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code that produces it, with &lt;code&gt;-Xmx3g&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLength&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repeat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;StringBuilder&lt;/code&gt; filled with 2,147,483,631 characters receives 100 more. The real-life case is an HTTP response accumulated without end, a multi-gigabyte file read whole into a &lt;code&gt;ByteArrayOutputStream&lt;/code&gt;, a SQL query with no &lt;code&gt;LIMIT&lt;/code&gt;. In practice, with a normally sized heap, those collections throw &lt;code&gt;Java heap space&lt;/code&gt; long before reaching 2 billion elements. All three messages therefore point at the same bug. If the stack trace shows &lt;code&gt;Arrays.copyOf&lt;/code&gt;, &lt;code&gt;grow&lt;/code&gt; or &lt;code&gt;newLength&lt;/code&gt;, look for the collection growing without bound, not for a leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  What looks like an OutOfMemoryError and isn't one
&lt;/h2&gt;

&lt;p&gt;Two out-of-memory deaths never go through a Java exception. They're more common than half of the cases above, and harder to read because there's no stack trace at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The container gets killed.&lt;/strong&gt; The process exceeds the cgroup memory limit, the kernel kills it with a &lt;code&gt;SIGKILL&lt;/code&gt;. The JVM has no time to say anything. All that's left is the exit code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker inspect app &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}'&lt;/span&gt;
&lt;span class="go"&gt;ExitCode=137 OOMKilled=true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the JVM was running with &lt;code&gt;-Xmx512m&lt;/code&gt; in a container limited to 128 MB. On Kubernetes, &lt;code&gt;kubectl describe pod&lt;/code&gt; shows &lt;code&gt;Reason: OOMKilled&lt;/code&gt; on the container's last state. The cause is almost always the same: heap plus the rest of the JVM exceeds the limit, because someone sized the heap without counting the rest. &lt;a href="https://lajvm.fr/en/tuning-the-jvm-in-a-container/" rel="noopener noreferrer"&gt;Tuning the JVM in a container&lt;/a&gt; explains how to leave the margin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JVM stops with an &lt;code&gt;hs_err&lt;/code&gt; file.&lt;/strong&gt; When it's the JVM itself that can't get memory from the system, to grow the heap for instance, it can't throw an exception. It writes a report and stops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000740000000, 3221225472, 0) failed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Not enough space'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;errno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;There is insufficient memory &lt;span class="k"&gt;for &lt;/span&gt;the Java Runtime Environment to &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Native memory allocation &lt;span class="o"&gt;(&lt;/span&gt;mmap&lt;span class="o"&gt;)&lt;/span&gt; failed to map 3221225472 bytes. Error detail: committing reserved memory.
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;An error report file with more information is saved as:
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/hs_err_pid269.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This case was reproduced on a VM with &lt;code&gt;vm.overcommit_memory=2&lt;/code&gt;, where the kernel refuses to commit more memory than it has. The &lt;code&gt;hs_err_pid&amp;lt;pid&amp;gt;.log&lt;/code&gt; file lists the possible causes, including the threads and their stacks. It also contains the memory state, the thread list and the JVM flags. Read it, that's what it's for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flags that react, and the ones that don't
&lt;/h2&gt;

&lt;p&gt;Three flags exist for that moment. &lt;code&gt;-XX:+HeapDumpOnOutOfMemoryError&lt;/code&gt; writes a dump. &lt;code&gt;-XX:+ExitOnOutOfMemoryError&lt;/code&gt; stops the JVM, so the orchestrator restarts a healthy process instead of leaving a zombie alive. &lt;code&gt;-XX:+CrashOnOutOfMemoryError&lt;/code&gt; does the same while producing an &lt;code&gt;hs_err&lt;/code&gt;, and a core dump if the system allows it.&lt;/p&gt;

&lt;p&gt;What the documentation doesn't say clearly is that these flags don't react to every message. They're wired into the virtual machine, on the path that throws the error for the heap and Metaspace. An error thrown by Java code goes right past them. I tested each case with &lt;code&gt;-XX:+ExitOnOutOfMemoryError&lt;/code&gt; and a &lt;code&gt;catch (OutOfMemoryError)&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Does the JVM exit?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Java heap space&lt;/td&gt;
&lt;td&gt;yes, &lt;code&gt;Terminating due to java.lang.OutOfMemoryError: Java heap space&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC overhead limit exceeded&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metaspace&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requested array size exceeds VM limit&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required array length N + M is too large&lt;/td&gt;
&lt;td&gt;no, the app keeps running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cannot reserve N bytes of direct buffer memory&lt;/td&gt;
&lt;td&gt;no, the app keeps running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unable to create native thread&lt;/td&gt;
&lt;td&gt;no, the app keeps running&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the errors thrown by Java code, the test output left no doubt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nl"&gt;caught:&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unable&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="kd"&gt;native&lt;/span&gt; &lt;span class="nl"&gt;thread:&lt;/span&gt; &lt;span class="n"&gt;possibly&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="n"&gt;reached&lt;/span&gt;
&lt;span class="n"&gt;still&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the worst-case scenario in production. A thread pool catches the error, logs it, and carries on with one thread fewer. The app stays alive, responds worse and worse, and the healthcheck stays green. For those messages you need an alert on the log or on a metric, not a JVM flag.&lt;/p&gt;

&lt;p&gt;Even without the flags, remember that a caught &lt;code&gt;OutOfMemoryError&lt;/code&gt; has cleaned up nothing. The test program catches it and prints &lt;code&gt;still running after the error&lt;/code&gt; in all six cases. What survived is a JVM where nobody knows which allocations failed halfway through. The right reflex is still to exit and let it restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  In short
&lt;/h2&gt;

&lt;p&gt;Read the text after &lt;code&gt;OutOfMemoryError:&lt;/code&gt;, it names the area. &lt;code&gt;Java heap space&lt;/code&gt; and &lt;code&gt;GC overhead limit exceeded&lt;/code&gt; are about the heap: heap dump, and tell the traffic spike from the leak. &lt;code&gt;Metaspace&lt;/code&gt; is about classes: look for &lt;code&gt;ClassLoader&lt;/code&gt; instances and set a limit so the error becomes readable. &lt;code&gt;Direct buffer memory&lt;/code&gt; is about NIO buffers, often dead and waiting for a GC. &lt;code&gt;Unable to create native thread&lt;/code&gt; is about a process limit, not memory: count the threads. &lt;code&gt;Requested array size&lt;/code&gt; is about a collection that grew without bound. And if the process dies with no exception, look at exit code 137 or the &lt;code&gt;hs_err&lt;/code&gt; file. Finally, set &lt;code&gt;-XX:+HeapDumpOnOutOfMemoryError&lt;/code&gt; and &lt;code&gt;-XX:+ExitOnOutOfMemoryError&lt;/code&gt; everywhere, knowing they only cover four messages out of six.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://lajvm.fr/en/the-6-jvm-outofmemoryerrors/" rel="noopener noreferrer"&gt;lajvm.fr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>jvm</category>
      <category>java</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Off-heap memory: where the JVM’s RAM goes</title>
      <dc:creator>Ghassane LATFI</dc:creator>
      <pubDate>Mon, 17 Aug 2026 19:38:38 +0000</pubDate>
      <link>https://dev.to/glatfi/off-heap-memory-where-the-jvms-ram-goes-41kk</link>
      <guid>https://dev.to/glatfi/off-heap-memory-where-the-jvms-ram-goes-41kk</guid>
      <description>&lt;p&gt;The heap is stable, the GC logs are clean, and yet the process RSS keeps climbing, day after day. A week later the pod gets &lt;code&gt;OOMKilled&lt;/code&gt;. You take a heap dump, go hunting for the leak, and find nothing. There's nothing to find: the memory that's overflowing isn't in the heap.&lt;/p&gt;

&lt;p&gt;A JVM consumes far more than its heap. Metaspace, thread stacks, direct buffers, JIT-compiled code, the GC's internal structures: all of it sits next to the heap, and none of it shows up in a heap dump. This article walks through that off-heap memory. What it's made of, how to measure it, and where to look when it grows.&lt;/p&gt;

&lt;p&gt;Before anything else, make sure the problem is actually off-heap. If the heap fills up and collections stop freeing anything, that's a regular leak: &lt;a href="https://lajvm.fr/en/diagnosing-a-memory-leak-with-a-heap-dump/" rel="noopener noreferrer"&gt;Diagnosing a memory leak with a heap dump&lt;/a&gt;. And if you suspect the container limits instead, &lt;a href="https://lajvm.fr/en/tuning-the-jvm-in-a-container/" rel="noopener noreferrer"&gt;Tuning the JVM in a container&lt;/a&gt; covers the basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What lives next to the heap
&lt;/h2&gt;

&lt;p&gt;The JVM process holds several memory areas, and the heap is just the biggest one. Around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Metaspace&lt;/strong&gt;. It stores the metadata of loaded classes: structure, methods, bytecode. It grows with the number of classes, and it has no limit by default.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;thread stacks&lt;/strong&gt;. Each platform thread reserves its stack, 1 MB by default on 64-bit Linux. With 500 threads, that can reach 500 MB.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;code cache&lt;/strong&gt;. The native code produced by the JIT, up to 240 MB reserved by default.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;GC structures&lt;/strong&gt;. G1 maintains tables to track references between regions. Expect a few percent of the heap size, sometimes more than 10%.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;direct buffers&lt;/strong&gt;. The &lt;code&gt;DirectByteBuffer&lt;/code&gt; objects allocated by NIO, Netty, or an HTTP client live entirely outside the heap.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;native memory from libraries&lt;/strong&gt;. Everything that goes through JNI allocates with &lt;code&gt;malloc&lt;/code&gt;, out of the JVM's sight: compression, cryptography, native drivers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add it all up and you see why a 1 GB container with a 768 MB heap ends up getting killed. The heap did nothing wrong. The rest just had no room left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring with Native Memory Tracking
&lt;/h2&gt;

&lt;p&gt;The JVM can break down where its memory goes. It's called Native Memory Tracking, and you enable it at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;-&lt;span class="n"&gt;XX&lt;/span&gt;:&lt;span class="n"&gt;NativeMemoryTracking&lt;/span&gt;=&lt;span class="n"&gt;summary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Oracle docs quote a 5 to 10% overhead, and summary mode costs less than that in practice. The flag does require a restart, though. Once the process is running, you query it with &lt;code&gt;jcmd&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;jcmd &amp;lt;pid&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VM.native_memory summary &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output lists each category, shortened here to the essentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total: reserved=6318MB, committed=1704MB
-       Java Heap (reserved=4096MB, committed=1024MB)
-           Class (reserved=1024MB, committed=12MB)
-          Thread (reserved=250MB, committed=84MB)
-            Code (reserved=245MB, committed=52MB)
-              GC (reserved=200MB, committed=98MB)
-           Other (reserved=310MB, committed=310MB)
-       Metaspace (reserved=128MB, committed=96MB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two numbers per line, and the difference matters. &lt;strong&gt;Reserved&lt;/strong&gt; is address space requested from the system, and it costs almost nothing. &lt;strong&gt;Committed&lt;/strong&gt; is memory the JVM has actually claimed, and it's the one that tracks the RSS. When you're hunting for where the RAM goes, read the committed column and ignore the other one.&lt;/p&gt;

&lt;p&gt;The categories speak for themselves. &lt;code&gt;Metaspace&lt;/code&gt; holds the class metadata. &lt;code&gt;Thread&lt;/code&gt; counts the stacks, and its committed number only counts the pages actually touched. &lt;code&gt;Other&lt;/code&gt; includes, among other things, the direct buffers. Here, 250 threads and 310 MB of direct buffers next to a 1 GB heap: that already explains a good chunk of a container.&lt;/p&gt;

&lt;p&gt;When you're chasing a leak, what you want is diff mode. Take a baseline, let the app run, then compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;jcmd &amp;lt;pid&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VM.native_memory baseline
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a few hours later
&lt;span class="gp"&gt;jcmd &amp;lt;pid&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VM.native_memory summary.diff &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows how each category moved since the baseline. The one that keeps climbing is your culprit, and you know right away where to dig.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual suspects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A growing Metaspace.&lt;/strong&gt; It grows when classes get loaded but never unloaded. The classic causes: hot redeployments, and above all classes generated on the fly (proxies, reflection, scripting engines). Since it has no limit by default, it can push the container into &lt;code&gt;OOMKilled&lt;/code&gt; without a sound. Setting &lt;code&gt;-XX:MaxMetaspaceSize=256m&lt;/code&gt; doesn't fix anything, but it turns the silent death into an &lt;code&gt;OutOfMemoryError: Metaspace&lt;/code&gt; with a stack trace, which is much easier to diagnose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too many threads.&lt;/strong&gt; Each platform thread pays for its stack. An unbounded pool, or pools created per request and never shut down, show up directly on NMT's &lt;code&gt;Thread&lt;/code&gt; line. A thread dump tells you how many there are and who created them. That's the subject of &lt;a href="https://lajvm.fr/en/reading-a-thread-dump-with-jstack/" rel="noopener noreferrer"&gt;Reading a thread dump with jstack&lt;/a&gt;. Virtual threads don't have this problem, since their stacks live in the heap: &lt;a href="https://lajvm.fr/en/virtual-threads-in-production/" rel="noopener noreferrer"&gt;Virtual threads in production&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct buffers piling up.&lt;/strong&gt; The nastiest trap. A &lt;code&gt;DirectByteBuffer&lt;/code&gt; allocates its memory outside the heap, but that memory is only returned to the system when the Java object itself gets collected by the GC. With a big heap and rare collections, dead buffers pile up waiting for a collection that never comes, and direct memory fills up while the heap sits half empty. The limit is set with &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt;, which defaults to the max heap size. You can watch the pool over JMX, through the &lt;code&gt;BufferPoolMXBean&lt;/code&gt; named &lt;code&gt;direct&lt;/code&gt;, which most metrics agents expose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code cache and the GC.&lt;/strong&gt; Rarely guilty. Their size settles after warmup. If they show up in an NMT diff after several days, that's unusual and worth a look, but always start with the three suspects above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NMT doesn't see
&lt;/h2&gt;

&lt;p&gt;NMT only tracks allocations made by the JVM itself. A native library that allocates with its own &lt;code&gt;malloc&lt;/code&gt; through JNI flies completely under the radar. The classic offender is zlib: an &lt;code&gt;Inflater&lt;/code&gt; or &lt;code&gt;Deflater&lt;/code&gt; that's never closed keeps its native buffer, and thousands of them add up to a real leak, invisible in NMT and in the heap dump alike.&lt;/p&gt;

&lt;p&gt;The allocator itself can also work against you. &lt;code&gt;glibc&lt;/code&gt; splits memory into arenas, 8 per core, and is bad at giving freed memory back to the system. If the app does a lot of native allocation, the RSS ends up well above the sum of the NMT categories, from fragmentation alone. The well-known fix in a container is a single environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the gap persists, the next step is to swap the allocator for &lt;code&gt;jemalloc&lt;/code&gt; and turn on its profiling. It records the native allocation stacks and points at the library responsible. It's heavier to set up, but it's the tool that finds the leaks nothing else can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  In short
&lt;/h2&gt;

&lt;p&gt;An RSS that climbs while the heap looks healthy means off-heap memory. Enable &lt;code&gt;-XX:NativeMemoryTracking=summary&lt;/code&gt;, read the committed column, and use &lt;code&gt;summary.diff&lt;/code&gt; mode to see which category is growing. Metaspace, threads and direct buffers explain most cases. If NMT sees nothing, look at native allocations from libraries and at glibc fragmentation. And in a container, always keep a margin between the heap and the memory limit: it's not waste, it's the room the rest of the JVM lives in.&lt;/p&gt;

</description>
      <category>java</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
