<?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: KiokuGraph</title>
    <description>The latest articles on DEV Community by KiokuGraph (@kiokugraph).</description>
    <link>https://dev.to/kiokugraph</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%2F4068210%2Feeee0199-670a-4c72-9307-4dab8d4b480a.jpg</url>
      <title>DEV Community: KiokuGraph</title>
      <link>https://dev.to/kiokugraph</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kiokugraph"/>
    <language>en</language>
    <item>
      <title>OutOfMemoryError - every variant, and what each one means</title>
      <dc:creator>KiokuGraph</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:10:32 +0000</pubDate>
      <link>https://dev.to/kiokugraph/outofmemoryerror-every-variant-and-what-each-one-means-3j5o</link>
      <guid>https://dev.to/kiokugraph/outofmemoryerror-every-variant-and-what-each-one-means-3j5o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ny6hhemaselfomlkggw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ny6hhemaselfomlkggw.png" alt="OutOfMemoryError - every variant, and what each one means" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are here from a search at an awkward hour, the answer is below the fold and the commands are copy-pasteable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Restarting makes it go away, which is exactly why it comes back on a schedule nobody has written down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually finds it
&lt;/h2&gt;

&lt;p&gt;Point KiokuGraph at the dump from the crash and it returns the dominator tree, the objects ranked by what they actually retain, and the line of your code responsible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the full article
&lt;/h2&gt;

&lt;p&gt;Details and the runbook: &lt;a href="https://kiokugraph.io/blog/outofmemoryerror-complete-guide/" rel="noopener noreferrer"&gt;https://kiokugraph.io/blog/outofmemoryerror-complete-guide/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyone else been bitten by this in production?&lt;/p&gt;

</description>
      <category>production</category>
      <category>jvm</category>
      <category>kiokugraph</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Dominator trees explained, without the graph theory</title>
      <dc:creator>KiokuGraph</dc:creator>
      <pubDate>Sat, 08 Aug 2026 01:49:20 +0000</pubDate>
      <link>https://dev.to/kiokugraph/dominator-trees-explained-without-the-graph-theory-m37</link>
      <guid>https://dev.to/kiokugraph/dominator-trees-explained-without-the-graph-theory-m37</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq63r8w3c63t9ox1z6we9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq63r8w3c63t9ox1z6we9.jpg" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open any heap analyser and you meet the word "dominator" in the first thirty seconds, usually with&lt;br&gt;
no explanation. It sounds like theory. It is the single most practical idea in memory analysis, and&lt;br&gt;
it takes about three minutes to understand properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;A heap is a graph: objects pointing at objects, with cycles, shared references and no tidy hierarchy.&lt;/p&gt;

&lt;p&gt;You want to answer one question: &lt;strong&gt;if I fix this, how much memory do I get back?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question is hard to answer from the graph directly. An object can be referenced from five&lt;br&gt;
places. Dropping one reference frees nothing, because the other four still hold it. Counting the&lt;br&gt;
bytes "under" an object by walking its references double-counts everything shared, and in a real&lt;br&gt;
heap almost everything is shared.&lt;/p&gt;

&lt;p&gt;So a naive answer overstates wildly. You need to know what an object holds &lt;strong&gt;exclusively&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The definition, in one sentence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;X dominates Y&lt;/strong&gt; if every path from the GC roots to Y passes through X.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is it. "Every path" is the load-bearing part.&lt;/p&gt;

&lt;p&gt;GC roots are where reachability begins - stack locals of live threads, static fields, JNI&lt;br&gt;
references. An object survives collection because some path leads to it from a root. If &lt;strong&gt;every&lt;/strong&gt;&lt;br&gt;
such path goes through X, then removing X removes the last way to reach Y, and Y becomes garbage.&lt;/p&gt;

&lt;p&gt;So dominance means: &lt;em&gt;X is single-handedly keeping Y alive.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [GC root]
            |
            v
          Cache
         /     \
        v       v
    Session   Session
        \      /
         v    v
        UserRecord
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cache&lt;/code&gt; dominates both &lt;code&gt;Session&lt;/code&gt; objects. Every path from the root reaches them through &lt;code&gt;Cache&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cache&lt;/code&gt; also dominates &lt;code&gt;UserRecord&lt;/code&gt;. There are &lt;em&gt;two&lt;/em&gt; paths to it, but &lt;strong&gt;both&lt;/strong&gt; pass through
&lt;code&gt;Cache&lt;/code&gt;, so it still qualifies.&lt;/li&gt;
&lt;li&gt;Neither &lt;code&gt;Session&lt;/code&gt; dominates &lt;code&gt;UserRecord&lt;/code&gt;. Remove the left one and the right still reaches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The consequence is the useful bit. Drop &lt;code&gt;Cache&lt;/code&gt; and all four objects below become collectable. Drop&lt;br&gt;
one &lt;code&gt;Session&lt;/code&gt; and &lt;strong&gt;nothing&lt;/strong&gt; is freed - &lt;code&gt;UserRecord&lt;/code&gt; survives via the other, and the &lt;code&gt;Session&lt;/code&gt;&lt;br&gt;
itself is only reclaimed if nothing else points at it.&lt;/p&gt;

&lt;p&gt;This is why "which object has the most references?" is the wrong question and "which object&lt;br&gt;
dominates the most bytes?" is the right one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retained size
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;retained size&lt;/strong&gt; of X is the total bytes of X plus everything X dominates. Equivalently: the&lt;br&gt;
memory that becomes free if X becomes unreachable.&lt;/p&gt;

&lt;p&gt;Contrast with &lt;strong&gt;shallow size&lt;/strong&gt;, the bytes of the object itself - for a &lt;code&gt;HashMap&lt;/code&gt;, about 48 bytes,&lt;br&gt;
regardless of whether it holds three entries or three million.&lt;/p&gt;

&lt;p&gt;That gap is what makes leaks findable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Shallow&lt;/th&gt;
&lt;th&gt;Retained&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HashMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;48 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.7 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;byte[]&lt;/code&gt; (one of 27,000)&lt;/td&gt;
&lt;td&gt;8 KB&lt;/td&gt;
&lt;td&gt;8 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;String&lt;/code&gt; (one of 1.2 M)&lt;/td&gt;
&lt;td&gt;40 B&lt;/td&gt;
&lt;td&gt;40 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A histogram sorted by instance count puts &lt;code&gt;String&lt;/code&gt; at the top and tells you nothing - a million&lt;br&gt;
strings is what a healthy application looks like. Sorted by retained size, that 48-byte &lt;code&gt;HashMap&lt;/code&gt;&lt;br&gt;
appears first, and it is the outage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tree
&lt;/h2&gt;

&lt;p&gt;Compute the dominator relationship for every object and you get a tree, because dominance has a&lt;br&gt;
convenient property: each object has exactly one &lt;strong&gt;immediate dominator&lt;/strong&gt;, the closest object that&lt;br&gt;
dominates it. Link each to its immediate dominator and the messy graph becomes a tree.&lt;/p&gt;

&lt;p&gt;The tree has two properties that make it worth the computation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No double-counting.&lt;/strong&gt; Every object appears exactly once. Retained sizes down the tree sum
correctly, so the numbers can be trusted and compared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sorting by retained size ranks your problems.&lt;/strong&gt; The top of the tree is, in order, the things
whose removal frees the most memory. That is a work queue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note what the tree is &lt;em&gt;not&lt;/em&gt;: it is not the reference graph. A dominator tree edge does not mean "X&lt;br&gt;
has a field pointing at Y". It means "X is the last thing standing between Y and the collector".&lt;/p&gt;




&lt;h2&gt;
  
  
  How to read it during an incident
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sort by retained size, descending.&lt;/strong&gt; Look at the top ten and no further at first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignore the framework.&lt;/strong&gt; The first entries are often a classloader, a thread pool or a
connection pool. Those legitimately dominate a lot. You are looking for something &lt;em&gt;yours&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for a small object with an enormous retained size.&lt;/strong&gt; A &lt;code&gt;static&lt;/code&gt; field, a cache, a listener
list. That asymmetry is the signature of a leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expand one level, not ten.&lt;/strong&gt; The children tell you &lt;em&gt;what&lt;/em&gt; is being retained - sessions? byte
arrays? - which usually names the bug immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then ask why it is still reachable.&lt;/strong&gt; The path from the GC root to that object is the code path
that has to change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1-4 usually take a couple of minutes. Step 5 is the actual debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it is computed, not estimated
&lt;/h2&gt;

&lt;p&gt;Dominance is a defined relation over a directed graph, not a heuristic. Two analysers over the same&lt;br&gt;
dump produce the same dominator tree, or one of them is wrong. There is no tuning parameter and&lt;br&gt;
nothing to interpret.&lt;/p&gt;

&lt;p&gt;That matters when you are about to tell a room that the leak is in the session cache. "The analyser&lt;br&gt;
thinks it might be here" invites argument. "Removing this frees 4.7 GB, and here is the reference&lt;br&gt;
chain keeping it alive" ends the discussion.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dominates&lt;/strong&gt; = every path from a GC root passes through it = it is single-handedly keeping the
object alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained size&lt;/strong&gt; = what you get back if it goes away. &lt;strong&gt;Shallow size&lt;/strong&gt; = the object alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort by retained size.&lt;/strong&gt; Instance counts find noise; retained size finds leaks.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;small object with a huge retained size&lt;/strong&gt; is the classic leak signature.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;mathematics, not inference&lt;/strong&gt; - which is why the number is arguable only if it is wrong.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://kiokugraph.io" rel="noopener noreferrer"&gt;KiokuGraph&lt;/a&gt; computes the dominator tree for JVM and .NET heaps and opens on&lt;br&gt;
it sorted by retained size, so steps 1-4 above are the first screen. Free for a single dump.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>jvm</category>
      <category>memory</category>
    </item>
    <item>
      <title>KiokuGraph - Heap Dump Analyzer for JVM, .NET, Android and Kotlin</title>
      <dc:creator>KiokuGraph</dc:creator>
      <pubDate>Sat, 08 Aug 2026 01:37:35 +0000</pubDate>
      <link>https://dev.to/kiokugraph/kiokugraph-4nnh</link>
      <guid>https://dev.to/kiokugraph/kiokugraph-4nnh</guid>
      <description>&lt;h1&gt;
  
  
  Reading an OutOfMemoryError: what the stack trace does and does not tell you
&lt;/h1&gt;

&lt;p&gt;At 02:14 your service stops serving and the log says this:&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="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="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;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Arrays&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;3512&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;StringBuilder&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="nc"&gt;StringBuilder&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;173&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;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CsvWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeRow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CsvWriter&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;88&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;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ReportJob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReportJob&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;141&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instinct is to open &lt;code&gt;CsvWriter.java:88&lt;/code&gt; and start optimising the string handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is almost always the wrong file.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj3qohkfjz3rifqqbsb0s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj3qohkfjz3rifqqbsb0s.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the stack trace actually says
&lt;/h2&gt;

&lt;p&gt;The trace shows &lt;strong&gt;which allocation failed&lt;/strong&gt;, not which code caused the heap to be full. Those are&lt;br&gt;
different questions with usually different answers.&lt;/p&gt;

&lt;p&gt;Think of it as a room filling with water. The stack trace names the last drop that made it overflow.&lt;br&gt;
It says nothing about the tap that has been running for six hours.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CsvWriter&lt;/code&gt; asked for a slightly larger &lt;code&gt;char[]&lt;/code&gt;. There was no room. &lt;code&gt;CsvWriter&lt;/code&gt; is the victim - it&lt;br&gt;
is simply the code unlucky enough to allocate at the moment the heap ran out. The real cause is&lt;br&gt;
whatever is holding memory it should have released.&lt;/p&gt;

&lt;p&gt;This is why the trace is often boringly generic. &lt;code&gt;Arrays.copyOf&lt;/code&gt;, &lt;code&gt;HashMap.resize&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;ArrayList.grow&lt;/code&gt; - these appear constantly in OOM traces not because collections are the problem,&lt;br&gt;
but because collections allocate frequently, so they are statistically likely to be holding the&lt;br&gt;
glass when it overflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The useful signal in the trace is small:&lt;/strong&gt; it tells you which &lt;em&gt;thread&lt;/em&gt; died, which sometimes tells&lt;br&gt;
you what the application was doing. Nothing more.&lt;/p&gt;


&lt;h2&gt;
  
  
  Read the message, not the trace
&lt;/h2&gt;

&lt;p&gt;The first line is more informative than everything under it, because the JVM uses distinct messages&lt;br&gt;
for genuinely distinct failures:&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;What it means&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Java heap space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The heap is full and GC cannot reclaim enough.&lt;/td&gt;
&lt;td&gt;A leak, or a heap too small for the workload. &lt;strong&gt;This is the common one.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GC overhead limit exceeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Over 98% of time in GC, recovering under 2%.&lt;/td&gt;
&lt;td&gt;Same causes as above, caught earlier. The JVM is thrashing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Metaspace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class metadata space exhausted.&lt;/td&gt;
&lt;td&gt;Classloader leak. Look at redeploys, dynamic proxies, scripting engines - &lt;strong&gt;not&lt;/strong&gt; your data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Requested array size exceeds VM limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A single array over ~2^31 elements.&lt;/td&gt;
&lt;td&gt;A bug: an unbounded read, a corrupt length field. Not a leak.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unable to create native thread&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The OS refused a thread.&lt;/td&gt;
&lt;td&gt;Thread leak or an OS limit. The heap may be fine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Direct buffer memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Off-heap &lt;code&gt;ByteBuffer&lt;/code&gt; exhausted.&lt;/td&gt;
&lt;td&gt;NIO or Netty buffers not being released. &lt;strong&gt;A bigger heap will not help.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these - &lt;code&gt;Metaspace&lt;/code&gt; and &lt;code&gt;Direct buffer memory&lt;/code&gt; - are frequently "fixed" by raising &lt;code&gt;-Xmx&lt;/code&gt;,&lt;br&gt;
which cannot possibly help because neither lives in the heap. If you take one thing from this&lt;br&gt;
article, take that.&lt;/p&gt;


&lt;h2&gt;
  
  
  Leak or undersized?
&lt;/h2&gt;

&lt;p&gt;Both produce &lt;code&gt;Java heap space&lt;/code&gt;. They need opposite fixes, and you cannot tell them apart from the&lt;br&gt;
trace. You need the shape of memory over time.&lt;/p&gt;

&lt;p&gt;Enable GC logging - it is cheap enough for production:&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="nt"&gt;-Xlog&lt;/span&gt;:gc&lt;span class="k"&gt;*&lt;/span&gt;:file&lt;span class="o"&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime:filecount&lt;span class="o"&gt;=&lt;/span&gt;5,filesize&lt;span class="o"&gt;=&lt;/span&gt;20M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then look at &lt;strong&gt;heap used after each full GC&lt;/strong&gt;, not peak usage. Peak is noise; the post-collection&lt;br&gt;
floor is the truth.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sawtooth that returns to the same floor&lt;/strong&gt; → not a leak. The workload needs more room than it
has, or allocation is too aggressive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Floor climbing steadily over hours or days&lt;/strong&gt; → a leak. Something retains objects across
collections, and no heap size will save you. It buys time proportional to the size increase, and
then fails again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A leak that takes a week to kill a 4 GB heap will take two weeks to kill an 8 GB one. Doubling&lt;br&gt;
&lt;code&gt;-Xmx&lt;/code&gt; is a scheduling decision, not a fix.&lt;/p&gt;


&lt;h2&gt;
  
  
  Get the dump
&lt;/h2&gt;

&lt;p&gt;The trace cannot tell you what is retained. Only the heap can, so capture it:&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="nt"&gt;-XX&lt;/span&gt;:+HeapDumpOnOutOfMemoryError
&lt;span class="nt"&gt;-XX&lt;/span&gt;:HeapDumpPath&lt;span class="o"&gt;=&lt;/span&gt;/var/dumps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes an &lt;code&gt;.hprof&lt;/code&gt; at the moment of failure, costs nothing until it triggers, and should be on&lt;br&gt;
in every production JVM. Without it, an OOM at 02:14 leaves you with a stack trace naming an&lt;br&gt;
innocent bystander.&lt;/p&gt;

&lt;p&gt;To capture from a &lt;em&gt;running&lt;/em&gt; JVM before it dies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jcmd &amp;lt;pid&amp;gt; GC.heap_dump /var/dumps/app.hprof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two warnings. It pauses the JVM for roughly a second per gigabyte - do it on one instance out of the&lt;br&gt;
pool. And the file is the size of your live set, so check disk first; a dump that fills the volume&lt;br&gt;
turns one incident into two.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then ask the only question that matters
&lt;/h2&gt;

&lt;p&gt;With a dump open, ignore what is &lt;em&gt;numerous&lt;/em&gt; and find what is &lt;em&gt;retained&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Those differ, and the distinction is the whole game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shallow size&lt;/strong&gt; - the bytes of one object alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained size&lt;/strong&gt; - the bytes that would be freed if it were collected: itself plus everything
only reachable through it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A million &lt;code&gt;String&lt;/code&gt; objects sounds alarming and usually is not - they are shared, referenced from&lt;br&gt;
everywhere, and collecting any one frees 40 bytes. One &lt;code&gt;HashMap&lt;/code&gt; with a shallow size of 48 bytes and&lt;br&gt;
a &lt;strong&gt;retained size of 4.7 GB&lt;/strong&gt; is your outage. It is the only thing holding those million strings,&lt;br&gt;
and dropping it frees all of them.&lt;/p&gt;

&lt;p&gt;Sort by retained size, look at the top ten, and the leak is almost always visibly there. In practice&lt;br&gt;
it is one of five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;static&lt;/code&gt; collection that is only ever added to&lt;/li&gt;
&lt;li&gt;Listeners or callbacks registered and never removed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThreadLocal&lt;/code&gt; values on a pooled thread that outlives the request&lt;/li&gt;
&lt;li&gt;A cache with no eviction policy&lt;/li&gt;
&lt;li&gt;A classloader retained by a stray reference, keeping an entire old deployment alive&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The stack trace names the &lt;strong&gt;failed allocation&lt;/strong&gt;, not the cause. Do not start there.&lt;/li&gt;
&lt;li&gt;Read the &lt;strong&gt;message&lt;/strong&gt;. &lt;code&gt;Metaspace&lt;/code&gt; and &lt;code&gt;Direct buffer memory&lt;/code&gt; are not heap problems and ignore &lt;code&gt;-Xmx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;GC logs&lt;/strong&gt; to tell a leak from an undersized heap: watch the floor after full GC, not the peak.&lt;/li&gt;
&lt;li&gt;Turn on &lt;code&gt;-XX:+HeapDumpOnOutOfMemoryError&lt;/code&gt; &lt;strong&gt;now&lt;/strong&gt;, before you need it.&lt;/li&gt;
&lt;li&gt;In the dump, sort by &lt;strong&gt;retained size&lt;/strong&gt;. The answer is nearly always in the top ten.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://kiokugraph.io" rel="noopener noreferrer"&gt;KiokuGraph&lt;/a&gt; computes dominator trees and retained sizes for JVM and .NET&lt;br&gt;
dumps, so the ranking above is the first screen rather than something you assemble by hand. Free for&lt;br&gt;
a single dump - no signup needed to try it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>heapdump</category>
      <category>jvm</category>
      <category>kiokugraph</category>
      <category>memory</category>
    </item>
  </channel>
</rss>
