<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>LucasBG0.com</title><link>https://lucasbg0.com/</link><description>Blog de Lucas Barbosa Gomes (LucasBG0) — DevOps, Kubernetes, Linux, cloud e engenharia de infraestrutura.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Fri, 12 Jun 2026 19:58:20 GMT</lastBuildDate><atom:link href="https://lucasbg0.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Como dimensionar memória Java no Kubernetes (MaxRAMPercentage e OOMKill)</title><link>https://lucasbg0.com/2026/06/09/jvm-memoria-em-containers/</link><guid isPermaLink="true">https://lucasbg0.com/2026/06/09/jvm-memoria-em-containers/</guid><pubDate>Tue, 09 Jun 2026 19:30:00 GMT</pubDate><description>&lt;p&gt;Esse post nasceu de um problema real. Uma frota de aplicações Java rodando em
Kubernetes tinha padronizado as JVMs com
&lt;code&gt;-XX:InitialRAMPercentage=75 -XX:MaxRAMPercentage=75&lt;/code&gt; e dimensionava &lt;code&gt;requests&lt;/code&gt;
e &lt;code&gt;limits&lt;/code&gt; de memória olhando &lt;code&gt;container.memory.usage&lt;/code&gt; e &lt;code&gt;working_set&lt;/code&gt; no
observability. No papel, parecia certo: a JVM pega 75% do limite, sobra 25% pro
resto, e a gente acompanha o RSS pra ajustar.&lt;/p&gt;
&lt;p&gt;Na prática, dois sintomas apareceram:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;O RSS de &lt;strong&gt;todas&lt;/strong&gt; as aplicações vivia colado em ~75% do limite — inclusive
em ambientes de &lt;code&gt;dev&lt;/code&gt; que mal recebiam tráfego. Não dava pra saber quem
estava desperdiçando.&lt;/li&gt;
&lt;li&gt;Quando tentamos reduzir os limites com base nesse RSS, várias aplicações
começaram a tomar &lt;strong&gt;OOMKill (exit 137)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esse é um daqueles casos em que a métrica que você está olhando está mentindo —
não porque está errada, mas porque você está interpretando a coisa errada. Vou
documentar o que descobri investigando dados de ambientes reais e, no fim,
montei uma &lt;strong&gt;PoC reproduzível&lt;/strong&gt; (Docker + Java 21) que comprova cada afirmação
com números medidos. Todo o código da PoC está no repositório público
&lt;a href="https://github.com/LucasBG0/poc-jvm-memory-containers"target="_blank" rel="noopener"&gt;LucasBG0/poc-jvm-memory-containers&lt;/a&gt; que roda com um &lt;code&gt;./run.sh&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esse post nasceu de um problema real. Uma frota de aplicações Java rodando em
Kubernetes tinha padronizado as JVMs com
<code>-XX:InitialRAMPercentage=75 -XX:MaxRAMPercentage=75</code> e dimensionava <code>requests</code>
e <code>limits</code> de memória olhando <code>container.memory.usage</code> e <code>working_set</code> no
observability. No papel, parecia certo: a JVM pega 75% do limite, sobra 25% pro
resto, e a gente acompanha o RSS pra ajustar.</p>
<p>Na prática, dois sintomas apareceram:</p>
<ol>
<li>O RSS de <strong>todas</strong> as aplicações vivia colado em ~75% do limite — inclusive
em ambientes de <code>dev</code> que mal recebiam tráfego. Não dava pra saber quem
estava desperdiçando.</li>
<li>Quando tentamos reduzir os limites com base nesse RSS, várias aplicações
começaram a tomar <strong>OOMKill (exit 137)</strong>.</li>
</ol>
<p>Esse é um daqueles casos em que a métrica que você está olhando está mentindo —
não porque está errada, mas porque você está interpretando a coisa errada. Vou
documentar o que descobri investigando dados de ambientes reais e, no fim,
montei uma <strong>PoC reproduzível</strong> (Docker + Java 21) que comprova cada afirmação
com números medidos. Todo o código da PoC está no repositório público
<a href="https://github.com/LucasBG0/poc-jvm-memory-containers"target="_blank" rel="noopener">LucasBG0/poc-jvm-memory-containers</a> que roda com um <code>./run.sh</code>.</p>
<blockquote>
  <p>Os números &ldquo;de produção&rdquo; ao longo do texto vêm de uma frota real de apps Java
em K8s, anonimizada. Os números &ldquo;da PoC&rdquo; são medidos na minha máquina e você
consegue reproduzir.</p>

</blockquote>
<h2>O básico que todo mundo confunde<span class="hx:absolute hx:-mt-20" id="o-básico-que-todo-mundo-confunde"></span>
    <a href="#o-b%c3%a1sico-que-todo-mundo-confunde" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de qualquer coisa, é preciso separar quatro flags que parecem fazer a
mesma coisa e não fazem.</p>
<h3><code>-Xms</code> e <code>-Xmx</code> (absolutos)<span class="hx:absolute hx:-mt-20" id="-xms-e--xmx-absolutos"></span>
    <a href="#-xms-e--xmx-absolutos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>São o tamanho <strong>inicial</strong> (<code>-Xms</code>) e <strong>máximo</strong> (<code>-Xmx</code>) do heap, em valores
absolutos (<code>-Xmx512m</code>). O problema clássico em container: por muito tempo, a JVM
<strong>não enxergava o cgroup</strong> e calculava esses valores com base na RAM da
<strong>máquina inteira</strong>. Você botava um limite de 512 MiB no pod e a JVM achava que
tinha 64 GiB pra brincar → OOMKill na primeira carga. Isso foi corrigido (JDK 8u191+
e 10+ são container-aware), mas o <code>-Xms</code>/<code>-Xmx</code> fixo continua sendo <strong>acoplado à
sua mão</strong>: se alguém muda o limite do container e esquece de mudar o <code>-Xmx</code>, os
dois desalinham.</p>
<h3><code>-XX:MaxRAMPercentage</code> (o teto, cgroup-aware)<span class="hx:absolute hx:-mt-20" id="-xxmaxrampercentage-o-teto-cgroup-aware"></span>
    <a href="#-xxmaxrampercentage-o-teto-cgroup-aware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Define o heap <strong>máximo</strong> como uma porcentagem da memória <strong>disponível</strong> (que, num
container, é o limite do cgroup). É o jeito moderno e recomendado: você muda o
limite do pod e o heap acompanha sozinho. <code>MaxRAMPercentage=75</code> num container de
768 MiB → heap máximo de 576 MiB.</p>
<h3><code>-XX:InitialRAMPercentage</code> (o inicial, cgroup-aware)<span class="hx:absolute hx:-mt-20" id="-xxinitialrampercentage-o-inicial-cgroup-aware"></span>
    <a href="#-xxinitialrampercentage-o-inicial-cgroup-aware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A mesma ideia, mas para o tamanho <strong>inicial</strong> do heap (o equivalente percentual
do <code>-Xms</code>). É aqui que mora boa parte da confusão deste post: <strong>definir um
<code>InitialRAMPercentage</code> alto não significa que sua app
precisa daquilo — significa que a JVM vai <em>comitar</em> aquilo no boot.</strong></p>
<h3><code>-XX:MinRAMPercentage</code> (a pegadinha)<span class="hx:absolute hx:-mt-20" id="-xxminrampercentage-a-pegadinha"></span>
    <a href="#-xxminrampercentage-a-pegadinha" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse é o nome mais traiçoeiro do JDK. <code>MinRAMPercentage</code> <strong>não</strong> define um piso
de heap, como o nome sugere. Ele só entra em ação quando a memória disponível é
<strong>pequena</strong> (abaixo de ~256 MiB por padrão) e, nesse caso, define o heap máximo
como aquela porcentagem. Para qualquer container com memória &ldquo;normal&rdquo;, o
<code>MinRAMPercentage</code> é <strong>simplesmente ignorado</strong> e quem manda é o
<code>MaxRAMPercentage</code>. Mais adiante eu provo isso com a PoC.</p>
<h2>A PoC: o que ela mede<span class="hx:absolute hx:-mt-20" id="a-poc-o-que-ela-mede"></span>
    <a href="#a-poc-o-que-ela-mede" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A PoC é um único programa Java (<code>MemReport.java</code>) que roda em &ldquo;source mode&rdquo; do
Java 21, dentro de uma imagem <code>eclipse-temurin:21-jdk</code>, com um limite de
container fixo via <code>docker run --memory</code>. Ele:</p>
<ul>
<li>retém um <strong>live set</strong> controlado (30 MiB de <code>byte[]</code> que sobrevivem ao GC) —
representa a memória que a app de fato precisa;</li>
<li>gera <strong>churn</strong> (lixo de vida curta) pra encher o eden;</li>
<li>tira dois retratos: <strong>BOOT</strong> (logo que sobe, antes de alocar) e <strong>POST</strong>
(depois de reter o live set + churn);</li>
<li>força um <code>System.gc()</code> no fim e mede o <strong>live set pós-GC</strong> (<code>old + survivor</code>);</li>
<li>lê <code>/sys/fs/cgroup/memory.current</code> e <code>memory.max</code> <strong>de dentro do container</strong>
pra reportar RSS e limite reais.</li>
</ul>
<p>Rodei sete cenários, todos com o mesmo live set de 30 MiB, variando só as flags.
Aqui está a tabela completa (valores em MiB):</p>
<table>
	<thead>
			<tr>
					<th>cenário</th>
					<th style="text-align: right">limit</th>
					<th style="text-align: right">heap max (Xmx)</th>
					<th style="text-align: right">committed BOOT</th>
					<th style="text-align: right">RSS BOOT</th>
					<th style="text-align: right">committed POST</th>
					<th style="text-align: right">RSS POST</th>
					<th style="text-align: right">heap used POST</th>
					<th style="text-align: right">non-heap committed</th>
					<th style="text-align: right">threads</th>
					<th style="text-align: right">live set (pós-GC)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>default</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">192</td>
					<td style="text-align: right">27</td>
					<td style="text-align: right">85</td>
					<td style="text-align: right">192</td>
					<td style="text-align: right">175</td>
					<td style="text-align: right">82</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>init-max-75</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">104</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">239</td>
					<td style="text-align: right">212</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>init-max-75-pretouch</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">645</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">654</td>
					<td style="text-align: right">212</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>low-init-max-75</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">118</td>
					<td style="text-align: right">108</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">392</td>
					<td style="text-align: right">187</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>xmx-xms</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">512</td>
					<td style="text-align: right">512</td>
					<td style="text-align: right">114</td>
					<td style="text-align: right">512</td>
					<td style="text-align: right">231</td>
					<td style="text-align: right">160</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>minram-small</code></td>
					<td style="text-align: right">200</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">25</td>
					<td style="text-align: right">67</td>
					<td style="text-align: right">100</td>
					<td style="text-align: right">122</td>
					<td style="text-align: right">70</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>minram-large</code></td>
					<td style="text-align: right">768</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">92</td>
					<td style="text-align: right">576</td>
					<td style="text-align: right">379</td>
					<td style="text-align: right">188</td>
					<td style="text-align: right">21</td>
					<td style="text-align: right">6</td>
					<td style="text-align: right">64</td>
			</tr>
			<tr>
					<td><code>oom-xmx-acima-do-limit</code></td>
					<td style="text-align: right">600</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right"><strong>exit 137</strong></td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
					<td style="text-align: right">—</td>
			</tr>
	</tbody>
</table>
<p>Vou destrinchar cada lição usando essa tabela.</p>
<h2>Armadilha #1: <code>InitialRAMPercentage</code> comita, mas não é &ldquo;uso&rdquo;<span class="hx:absolute hx:-mt-20" id="armadilha-1-initialrampercentage-comita-mas-não-é-uso"></span>
    <a href="#armadilha-1-initialrampercentage-comita-mas-n%c3%a3o-%c3%a9-uso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olhe a coluna <strong>committed BOOT</strong> (heap comitado logo no boot, antes de qualquer
alocação):</p>
<ul>
<li><code>default</code> (Initial padrão ~1,5%): <strong>27 MiB</strong></li>
<li><code>low-init-max-75</code> (Initial=15%): <strong>118 MiB</strong></li>
<li><code>init-max-75</code> (Initial=75%): <strong>576 MiB</strong></li>
<li><code>xmx-xms</code> (<code>-Xms512m</code>): <strong>512 MiB</strong></li>
</ul>
<p>Ou seja: <code>InitialRAMPercentage</code>/<code>-Xms</code> controla <strong>quanto heap a JVM reserva e
comita já no startup</strong>, independente de a app precisar. Com <code>Initial=75</code>, a JVM
comita 576 MiB de heap antes da app fazer qualquer coisa útil.</p>
<p>Mas — e esse &ldquo;mas&rdquo; é o coração do problema — <strong>comitar não é tocar</strong>. Repare na
coluna <strong>RSS BOOT</strong>: mesmo comitando 576 MiB de heap, o <code>init-max-75</code> boota com
RSS de só <strong>104 MiB</strong>, praticamente igual ao <code>default</code> (85 MiB). O kernel só
conta no RSS as páginas que foram <strong>realmente acessadas</strong> (page fault). Heap
comitado e não tocado é endereço reservado, não memória física.</p>
<p>Aqui está o retrato de boot do cenário <code>init-max-75</code>, direto do log da PoC:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">### BOOT (before allocating)
</span></span><span class="line"><span class="cl">  container limit (cgroup) : 768 MiB
</span></span><span class="line"><span class="cl">  container RSS   (cgroup) : 120 MiB
</span></span><span class="line"><span class="cl">  heap max (effective Xmx) : 576 MiB
</span></span><span class="line"><span class="cl">  heap used                : 10 MiB
</span></span><span class="line"><span class="cl">  heap committed           : 576 MiB   &lt;-- committed 75% of limit</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Então por que em produção o RSS vivia colado em 75%?<span class="hx:absolute hx:-mt-20" id="então-por-que-em-produção-o-rss-vivia-colado-em-75"></span>
    <a href="#ent%c3%a3o-por-que-em-produ%c3%a7%c3%a3o-o-rss-vivia-colado-em-75" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Duas razões, e a PoC mostra as duas.</p>
<p><strong>(a) <code>AlwaysPreTouch</code>.</strong> Se a JVM sobe com <code>-XX:+AlwaysPreTouch</code> (comum em setups
que fixam o heap pra ter latência previsível), ela <strong>toca todas as páginas
comitadas no boot</strong>. Veja o cenário <code>init-max-75-pretouch</code>: RSS BOOT salta de 104
para <strong>645 MiB</strong>. Aí sim o RSS reflete o <code>committed</code>, não a demanda.</p>
<p><strong>(b) Heap fixo + carga real.</strong> Com <code>Initial = Max</code>, o heap nunca encolhe, e à
medida que a app trabalha (alocação, evacuação de GC), as páginas vão sendo
tocadas até o RSS encostar no comitado e ficar lá. Em produção, com tráfego
contínuo por dias, é exatamente o que acontece: o RSS satura em ~75% e fica.</p>
<p>O resultado é o mesmo dos dois jeitos: <strong><code>working_set</code>/<code>container.memory.usage</code>
deixam de refletir a demanda real e passam a marcar ~75% do limite pra todo
mundo.</strong> Foi por isso que o <code>dev</code>, sem carga, aparecia com o mesmo RSS de
produção. Dimensionar <code>request</code> por esse número é dimensionar pelo seu próprio
<code>MaxRAMPercentage</code>, não pela necessidade da app.</p>
<h2>Armadilha #2: o &ldquo;uso real&rdquo; é o live set pós-GC, e ele é invariante<span class="hx:absolute hx:-mt-20" id="armadilha-2-o-uso-real-é-o-live-set-pós-gc-e-ele-é-invariante"></span>
    <a href="#armadilha-2-o-uso-real-%c3%a9-o-live-set-p%c3%b3s-gc-e-ele-%c3%a9-invariante" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se o RSS mente, qual número não mente? O <strong>live set</strong>: o que sobra no heap
<strong>depois</strong> de um GC, ou seja, os objetos que a app realmente segura.</p>
<p>A JVM divide o heap em gerações. A identidade é exata:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">heap_used = eden + survivor + old</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<ul>
<li><strong>eden</strong>: onde objetos novos nascem. É <strong>churn</strong> — lixo de vida curta que o GC
varre. Cresce e encolhe junto com o heap disponível.</li>
<li><strong>survivor + old</strong>: o que <strong>sobreviveu</strong> ao GC. Esse é o <strong>live set</strong> — a
memória que a app de fato retém.</li>
</ul>
<p>A prova está na PoC. Em <strong>todos</strong> os sete cenários que rodaram até o fim, o live
set pós-GC deu exatamente <strong>64 MiB</strong> (última coluna), porque é sempre a mesma
app retendo os mesmos blocos. (Retive 30 arrays de 1 MiB, mas cada um estoura uma
região do G1 e arredonda pra duas → ~60 MiB de objetos humongous + classes
retidas ≈ 64 MiB; o detalhe não importa, o que importa é que é <strong>constante</strong>.) A
configuração de heap não muda o que a app precisa — só muda quanto de espaço
sobra em volta.</p>
<p>Agora olhe o efeito colateral perverso na coluna <strong>heap used POST</strong> (pico de heap
usado durante o churn):</p>
<ul>
<li><code>default</code> (heap de 192 MiB): pico de <strong>82 MiB</strong></li>
<li><code>init-max-75</code> (heap de 576 MiB): pico de <strong>212 MiB</strong></li>
</ul>
<p>Mesma app, mesmo live set de 64 MiB, mas o pico de <code>heap_used</code> é <strong>2,5x maior</strong>
só porque o heap é maior. Por quê? Heap maior → o GC roda <strong>menos vezes</strong> → mais
lixo flutuante (eden + objetos mortos ainda não coletados) se acumula entre as
coletas. Esse é mais um motivo pelo qual olhar o <strong>pico</strong> de <code>heap_used</code> (ou o
RSS, que segue o heap tocado) superestima a necessidade real. O número honesto é
o <strong>vale pós-GC</strong>: <code>old + survivor</code>.</p>
<p>As métricas que sobrevivem à distorção, então, são:</p>
<ul>
<li><code>jvm.gc.old_gen_size</code> + <code>jvm.gc.survivor_size</code> → live set (heap retido);</li>
<li><code>jvm.non_heap_memory</code> (Metaspace, Code Cache, Compressed Class) → fora do heap,
cresce sob demanda;</li>
<li><code>jvm.buffer_pool.direct.used</code> (DirectByteBuffer) e <code>jvm.thread_count</code> (≈ 1 MiB
de pilha por thread) → memória <strong>nativa</strong>, off-heap, mas que conta no RSS.</li>
</ul>
<p>E as que você deve <strong>parar de usar</strong> pra dimensionar enquanto <code>Initial</code> for alto:
<code>container.memory.usage</code>, <code>working_set</code> e <code>jvm.heap_memory_committed</code> — todas
infladas.</p>
<h2>Armadilha #3: a que cobra a conta — OOMKill 137<span class="hx:absolute hx:-mt-20" id="armadilha-3-a-que-cobra-a-conta--oomkill-137"></span>
    <a href="#armadilha-3-a-que-cobra-a-conta--oomkill-137" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está a parte que custou caro. De posse do &ldquo;uso real&rdquo;, a primeira tentativa
foi a fórmula intuitiva:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">request = limit = uso_real_live × 1.2</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pegava <code>old + survivor + non_heap_used + direct + threads</code>, multiplicava por 1,2
de folga, e cortava o limite. Parecia ótimo no dashboard: economia de ~50%.</p>
<p>Resultado em <code>dev</code>/<code>qa</code>: uma série de aplicações tomando <strong>OOMKill (exit 137)</strong>.</p>
<p>A causa raiz tinha duas partes, ambas ignoradas pela fórmula ingênua:</p>
<ol>
<li><strong><code>non_heap_committed</code>, não <code>non_heap_used</code>.</strong> O Metaspace e o Code Cache
reservam (committed) blocos um pouco acima do que usam e quase nunca devolvem.
É o <code>committed</code> que conta no RSS, não o <code>used</code>. A diferença de um para outro é pouca,
mas vale a pena por conservadorismo utilizar <code>non_heap_committed</code>.</li>
<li><strong>Overhead nativo invisível.</strong> Estruturas internas do GC e do JIT, page cache
e, principalmente, <strong>agentes de APM/monitoramento</strong> (Datadog Agent, New Relic,
AppDynamics, Elastic APM…) — nada disso aparece nas métricas <code>jvm.*</code>, mas tudo
ocupa RSS. Reconciliando contra o RSS real nessa frota, esse resíduo nativo era
de <strong>66–254 MiB</strong> (média ~130 MiB). A constante de <strong>150 MiB</strong> foi o valor que
funcionou pra esse conjunto de serviços; o número certo pra sua frota depende
do que está rodando dentro do container. O jeito de calibrar está descrito na
seção da fórmula, mais abaixo.</li>
</ol>
<p>Some os dois e dá pra ver por que limites cortados &ldquo;na fórmula live × 1,2&rdquo;
batiam abaixo do <strong>piso físico</strong> da JVM e morriam.</p>
<p>A PoC reproduz o exit 137 de forma determinística no cenário
<code>oom-xmx-acima-do-limit</code>: um container de 600 MiB com <code>-Xms700m -Xmx700m -XX:+AlwaysPreTouch</code>. O heap configurado (700 MiB) não cabe no container (600
MiB), e o <code>AlwaysPreTouch</code> tenta tocar tudo no boot:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&gt;&gt; scenario: oom-xmx-acima-do-limit  (--memory=600m)  flags: -Xms700m -Xmx700m -XX:+AlwaysPreTouch
</span></span><span class="line"><span class="cl">   [!] container exited with code 137 (137 = OOMKill)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É o mesmo mecanismo, mais explícito: quando <code>heap_max + non_heap_committed + nativo</code> ultrapassa o <code>limit</code>, o kernel mata. A diferença é que em produção isso
acontecia silenciosamente porque ninguém somava o non-heap e o nativo na conta.</p>
<h2>A fórmula que sobrou<span class="hx:absolute hx:-mt-20" id="a-fórmula-que-sobrou"></span>
    <a href="#a-f%c3%b3rmula-que-sobrou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois dos 137, a fórmula de sizing virou esta (política <code>request = limit</code>, ou
seja, QoS <strong>Guaranteed</strong> — a JVM tende a crescer até o teto, então não adianta
deixar <code>request &lt; limit</code>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">limit = live_heap_pico / occ
</span></span><span class="line"><span class="cl">      + non_heap_committed
</span></span><span class="line"><span class="cl">      + direct.used
</span></span><span class="line"><span class="cl">      + thread_count × 1 MiB
</span></span><span class="line"><span class="cl">      + N MiB   (resíduo nativo: calibrado por frota — veja abaixo)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Os termos, mapeados nas métricas que sobrevivem à distorção:</p>
<table>
	<thead>
			<tr>
					<th>termo</th>
					<th>métrica</th>
					<th>papel</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>live_heap_pico</code></td>
					<td><code>max(old_gen_size + survivor_size)</code></td>
					<td>heap que a app retém</td>
			</tr>
			<tr>
					<td><code>occ</code></td>
					<td>—</td>
					<td>ocupação-alvo do heap (live como fração do heap)</td>
			</tr>
			<tr>
					<td><code>non_heap_committed</code></td>
					<td><code>max(non_heap_memory_committed)</code></td>
					<td>Metaspace + Code Cache (reservado)</td>
			</tr>
			<tr>
					<td><code>direct.used</code></td>
					<td><code>max(buffer_pool.direct.used)</code></td>
					<td>DirectByteBuffer (nativo)</td>
			</tr>
			<tr>
					<td><code>thread_count × 1 MiB</code></td>
					<td><code>max(thread_count)</code></td>
					<td>pilhas de thread (nativo)</td>
			</tr>
			<tr>
					<td><code>N MiB</code></td>
					<td>constante calibrada</td>
					<td>overhead nativo sem métrica direta</td>
			</tr>
	</tbody>
</table>
<h3>Como calibrar o resíduo nativo (<code>N</code>)<span class="hx:absolute hx:-mt-20" id="como-calibrar-o-resíduo-nativo-n"></span>
    <a href="#como-calibrar-o-res%c3%adduo-nativo-n" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><code>N</code> não tem uma métrica JVM dedicada porque ele vive fora do heap e fora do
non-heap gerenciado. Ele é, na prática, a diferença entre o RSS medido e tudo o
que você consegue somar diretamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">N  ≈  RSS_estável  −  heap_committed  −  non_heap_committed  −  direct.used  −  (threads × 1 MiB)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <strong>RSS estável</strong> é o <code>container.memory.usage</code> (ou <code>memory.current</code> do cgroup)
lido quando a app está aquecida e sob carga representativa, mas <strong>sem</strong> usar
<code>InitialRAMPercentage</code> alto nem <code>AlwaysPreTouch</code> — senão o RSS reflete heap
comitado e não tocado, e o <code>N</code> calculado fica inflado artificialmente. Use a
configuração com <code>InitialRAMPercentage</code> baixo (ex.: 25%) para essa medição.</p>
<p>Os principais componentes que compõem esse resíduo:</p>
<ul>
<li><strong>Agente de APM</strong> (Datadog Agent, New Relic, AppDynamics, Elastic APM…): o
agente Java attacha como um <code>-javaagent</code> e aloca memória nativa própria — de 30
a 100+ MiB dependendo do agente e do nível de instrumentação ativo.</li>
<li><strong>Exporter de métricas</strong> (Prometheus JMX Exporter, Micrometer…): menor impacto,
mas não zero.</li>
<li><strong>Thread stacks nativas</strong> além do <code>1 MiB</code> nominal: a pilha real de cada thread
(padrão <code>-Xss1m</code> no Linux) mais as estruturas de kernel associadas.</li>
<li><strong>Overhead interno do GC</strong>: o G1GC mantém estruturas de card table, remembered
sets e bitmaps de marcação que escalam com o tamanho do heap (tipicamente 1–5%
do heap max).</li>
<li><strong>Page cache e buffers de I/O do kernel</strong>: arquivos mapeados por <code>mmap</code>,
buffers de rede — o kernel conta no RSS do processo.</li>
</ul>
<p>Na frota que originou este post, <code>N = 150 MiB</code> cobriu bem a maioria dos serviços
(range real: 66–254 MiB). Se você usa um agente de APM pesado ou tem muitos
threads, <strong>meça e ajuste</strong>; 150 MiB é um ponto de partida, não uma constante
universal. O pior caso é subestimar: você vai ver OOMKill. O segundo pior é
superestimar muito: você desperdiça memória mas a app não morre.</p>
<p>Com as métricas coletadas da JVM, dá pra montar um dashboard que aplica essa
fórmula automaticamente e exibe a recomendação de <code>request</code>/<code>limit</code> por serviço:</p>
<p><img src="/2026/06/09/jvm-memoria-em-containers/example-dashboard-jvm-recomendation.png" alt="Dashboard com métricas JVM e recomendação de request/limit calculada"  loading="lazy" /></p>
<h3>Por que dividir por <code>occ</code>?<span class="hx:absolute hx:-mt-20" id="por-que-dividir-por-occ"></span>
    <a href="#por-que-dividir-por-occ" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O heap precisa ser <strong>maior</strong> que o live set pra caber a alocação de eden entre
GCs, o espaço de trabalho de evacuação do G1 e picos. Regra prática: o live set
não deve passar de ~70% do heap, senão o GC entra em thrashing (full GCs
encadeados → CPU alta → OOM por <em>GC overhead limit</em>).</p>
<ul>
<li><code>occ = 0,70</code> (agressivo): <code>heap = live × 1,43</code>. Economiza mais, GC mais
frequente.</li>
<li><code>occ = 0,60</code> (recomendado): <code>heap = live × 1,67</code>. Mais folga, menos GC, um
pouco mais de memória.</li>
</ul>
<p>O trade-off central é <strong>memória ↔ CPU/segurança</strong>. Ficamos com <code>0,60</code> como
padrão.</p>
<h3>Duas formas equivalentes de aplicar<span class="hx:absolute hx:-mt-20" id="duas-formas-equivalentes-de-aplicar"></span>
    <a href="#duas-formas-equivalentes-de-aplicar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O heap-alvo (<code>live/occ</code>) é o mesmo; muda só como você escreve:</p>
<p><strong>(A) Percentual:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">-XX:InitialRAMPercentage=&lt;P&gt; -XX:MaxRAMPercentage=&lt;P&gt;
</span></span><span class="line"><span class="cl">onde P = (live/occ) / limit × 100</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O heap acompanha o limite e <strong>nunca passa dele</strong> — o <code>MaxRAMPercentage</code> te dá esse
guardrail de graça.</p>
<p><strong>(B) Explícito:</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">-Xms&lt;live/occ&gt; -Xmx&lt;live/occ&gt;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Direto, mas <strong>desacoplado do limite</strong>. Exige um guardrail no Helm/CI garantindo
que <code>-Xmx + non_heap + nativo ≤ limit</code>, senão você cai no exit 137
do cenário da PoC.</p>
<p>Nas apps reais de microserviços, esse <code>P</code> caiu pra faixa de <strong>26–45%</strong> — bem longe dos 75%
padronizados. Era esse o desperdício.</p>
<h2>Armadilha bônus: <code>MaxRAMPercentage</code> alto embute risco de OOM<span class="hx:absolute hx:-mt-20" id="armadilha-bônus-maxrampercentage-alto-embute-risco-de-oom"></span>
    <a href="#armadilha-b%c3%b4nus-maxrampercentage-alto-embute-risco-de-oom" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem um detalhe estrutural perigoso. Como <code>MaxRAMPercentage</code> amarra o
<code>heap_max</code> ao limite, em apps pequenas o <strong>piso da JVM</strong> já pode estourar o
limite:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">heap_max (75% de 768) = 576 MiB
</span></span><span class="line"><span class="cl">+ non_heap_committed   = 227 MiB   (caso real)
</span></span><span class="line"><span class="cl">= 803 MiB  &gt;  limit de 768 MiB</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isso <strong>antes</strong> de contar o overhead nativo. Ou seja: com <code>MaxRAMPercentage=75</code>,
uma app de non-heap gordo já nasce com o teto teórico acima do limite. Funciona
enquanto o heap não enche — mas é uma bomba-relógio. A mitigação imediata em
produção foi baixar <code>Initial/Max</code> de 75 → 65 nas apps mais apertadas, e depois
aplicar a fórmula por serviço.</p>
<h2>A pegadinha do <code>MinRAMPercentage</code>, provada<span class="hx:absolute hx:-mt-20" id="a-pegadinha-do-minrampercentage-provada"></span>
    <a href="#a-pegadinha-do-minrampercentage-provada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Voltando ao nome traiçoeiro. Compare os dois cenários da PoC, ambos com
<code>-XX:MinRAMPercentage=50 -XX:MaxRAMPercentage=75</code>:</p>
<ul>
<li><code>minram-small</code> (container de <strong>200 MiB</strong>): heap max = <strong>100 MiB</strong> = 50% de 200
→ quem governou foi o <strong><code>MinRAMPercentage</code></strong>.</li>
<li><code>minram-large</code> (container de <strong>768 MiB</strong>): heap max = <strong>576 MiB</strong> = 75% de 768
→ o <code>MinRAMPercentage</code> foi <strong>ignorado</strong>, governou o <code>MaxRAMPercentage</code>.</li>
</ul>
<p>A regra: abaixo de ~256 MiB de memória disponível, o <code>MinRAMPercentage</code> define o
teto; acima disso, ele não faz nada. Na prática, para 99% dos containers de app,
<strong>configurar <code>MinRAMPercentage</code> não tem efeito nenhum</strong> — e é fonte recorrente de
confusão. Se você quer controlar o heap, o lever é o <code>MaxRAMPercentage</code>.</p>
<h2>Checklist de boas práticas<span class="hx:absolute hx:-mt-20" id="checklist-de-boas-práticas"></span>
    <a href="#checklist-de-boas-pr%c3%a1ticas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que ficou de aprendizado, resumido:</p>
<ol>
<li><strong>Não dimensione memória de JVM por <code>container.memory.usage</code>/<code>working_set</code></strong>
se você usa <code>InitialRAMPercentage</code> alto ou <code>AlwaysPreTouch</code>. Esses números
marcam ~<code>MaxRAMPercentage</code> do limite, não a demanda.</li>
<li><strong>Meça o uso real pelo live set pós-GC</strong>: <code>old_gen_size + survivor_size</code>.
Eden é churn, não soma.</li>
<li><strong>Não esqueça do não-heap e do nativo.</strong> <code>non_heap_committed</code> + pilhas de
thread + direct buffers + resíduo nativo (<code>N</code>). Foi ignorá-los que gerou os
OOMKill 137. O <code>N</code> depende do que roda no container — calibre medindo
<code>RSS_estável − heap_committed − non_heap_committed − direct − threads×1MiB</code>.
150 MiB funcionou nessa frota; a sua pode ser diferente.</li>
<li><strong><code>request = limit</code> (Guaranteed)</strong> para JVM — ela cresce até o teto.</li>
<li><strong><code>MaxRAMPercentage</code> vs <code>-Xmx</code> fixo</strong>: <code>MaxRAMPercentage</code> acompanha o limite e te dá
guardrail. Se usar <code>-Xmx</code>, ponha um guardrail explícito no CI/Helm.</li>
<li><strong><code>MinRAMPercentage</code> quase nunca é o que você quer.</strong> Só atua em containers
minúsculos (&lt;256 MiB).</li>
<li><strong>Cuidado com <code>MaxRAMPercentage</code> alto em apps pequenas</strong>: <code>heap_max + non_heap_committed</code> pode já passar do limite.</li>
</ol>
<h2>Apêndice: rodando a PoC<span class="hx:absolute hx:-mt-20" id="apêndice-rodando-a-poc"></span>
    <a href="#ap%c3%aandice-rodando-a-poc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tudo está em <a href="https://github.com/LucasBG0/poc-jvm-memory-containers"target="_blank" rel="noopener"><code>poc</code></a>.
Pré-requisito: Docker com cgroup v2 — roda na imagem <code>eclipse-temurin:21-jdk</code>).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@github.com:LucasBG0/poc-jvm-memory-containers.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> poc
</span></span><span class="line"><span class="cl">./run.sh</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O script builda a imagem, roda os sete cenários no mesmo limite de container e
gera <code>results.md</code> (a tabela deste post) e um <code>logs/&lt;cenário&gt;.log</code> com os dois
retratos de memória de cada execução. Os cenários cobrem:</p>
<ul>
<li><code>default</code>, <code>init-max-75</code>, <code>init-max-75-pretouch</code>, <code>low-init-max-75</code> — o efeito
do <code>Initial</code>/<code>Max</code> no committed, no RSS e no pico de heap;</li>
<li><code>xmx-xms</code> — a forma explícita, equivalente ao percentual;</li>
<li><code>minram-small</code> / <code>minram-large</code> — a pegadinha do <code>MinRAMPercentage</code>;</li>
<li><code>oom-xmx-acima-do-limit</code> — o exit 137 determinístico.</li>
</ul>
<p>Os números variam um pouco entre execuções (o RSS é instantâneo e oscila com o
GC), mas os sinais determinísticos — heap comitado no boot, teto de heap, RSS com
<code>AlwaysPreTouch</code>, live set pós-GC — são estáveis e contam a história toda.</p>
]]></content:encoded><category>java</category><category>jvm</category><category>kubernetes</category><category>oomkill</category><category>maxrampercentage</category><category>docker</category><category>g1gc</category><category>heap</category><category>cgroups</category><category>sre</category><category>performance</category></item><item><title>Sobre</title><link>https://lucasbg0.com/about/</link><guid isPermaLink="true">https://lucasbg0.com/about/</guid><pubDate>Mon, 01 Jan 0001 00:00:00 GMT</pubDate><description>&lt;p&gt;Meu nome é Lucas Barbosa Gomes, sou DevOps/SRE/Platform Engineer ou seja lá qual será o próximo nome de cargo que irão inventar rsrsrs. Tenho quase uma década de experiência profissional, especializado em arquiteturas cloud-native e Kubernetes. Atualmente trabalho como DevOps Engineer Sênior, focado em alta disponibilidade, automação de infraestrutura, práticas DevSecOps e crio soluções escaláveis para facilitar a vida dos Devs. Sou certificado &lt;strong&gt;CKA (Certified Kubernetes Administrator)&lt;/strong&gt;, &lt;strong&gt;Azure Administrator Associate (AZ-104)&lt;/strong&gt; e &lt;strong&gt;Azure DevOps Engineer Expert (AZ-400)&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Meu nome é Lucas Barbosa Gomes, sou DevOps/SRE/Platform Engineer ou seja lá qual será o próximo nome de cargo que irão inventar rsrsrs. Tenho quase uma década de experiência profissional, especializado em arquiteturas cloud-native e Kubernetes. Atualmente trabalho como DevOps Engineer Sênior, focado em alta disponibilidade, automação de infraestrutura, práticas DevSecOps e crio soluções escaláveis para facilitar a vida dos Devs. Sou certificado <strong>CKA (Certified Kubernetes Administrator)</strong>, <strong>Azure Administrator Associate (AZ-104)</strong> e <strong>Azure DevOps Engineer Expert (AZ-400)</strong>.</p>
<h2>Minha trajetória<span class="hx:absolute hx:-mt-20" id="minha-trajetória"></span>
    <a href="#minha-trajet%c3%b3ria" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Minha entrada no mundo da tecnologia veio de cursos técnicos — informática, montagem e manutenção de computadores e, mais tarde, computação gráfica. O que me puxou no começo foi a vontade de fazer jogos (era viciado em vídeo game), mas o que ficou de verdade foi a curiosidade de entender como as coisas funcionam por dentro.</p>
<p>Comecei a carreira ainda na faculdade, como estagiário num <strong>portal de notícias</strong>, montando newsletters num trabalho bastante manual e repetitivo. Foi ali que descobri o que realmente gosto de fazer: em vez de repetir tarefas manuais, comecei a automatizá-las, desenvolver ferramentas e provisionar ambientes. Foram 4 anos em que aprendi muito sobre desenvolvimento em um produto real — foi onde tive meu primeiro contato com WordPress e onde desenvolvi, na marra, o hábito de aprender por conta própria. Acabei virando responsável pelo departamento de TI, e essa autonomia me deixou focar justamente na parte que me interessava: desenvolvimento, automação e infraestrutura.</p>
<p>Numa <strong>agência especializada em WordPress</strong>, meu desenvolvimento em práticas DevOps acelerou bastante. Foi onde senti na pele a importância de soluções escaláveis e o custo real de pipelines replicadas e processos repetitivos. Aprofundei conhecimentos em Cloud e em ferramentas de IaC como Ansible e shell script, trabalhando lado a lado com mais desenvolvedores e vendo a operação em escala. Lá, eu elevei as boas práticas nos clientes usando esse CMS para um alto nível, em segurança e performance.</p>
<p>Depois vim para <strong>uma consultoria de tecnologia</strong>, onde aprofundei ainda mais em Kubernetes e plataforma em produção — plantões, incidentes e mudanças controladas — com responsabilidade pela plataforma de CI/CD no GitLab e por clusters Kubernetes em produção (EKS e AKS).</p>
<p>Também participei da liderança técnica de migrações estruturais — de ambientes on-premises para cloud e de clusters Kubernetes para Azure — além de evoluir bibliotecas compartilhadas de pipelines, automações operacionais e infraestrutura como código com Terraform, Helm, ArgoCD e Ansible.</p>
<h2>Sobre o blog<span class="hx:absolute hx:-mt-20" id="sobre-o-blog"></span>
    <a href="#sobre-o-blog" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse blog é um espaço para documentar experiências reais — o que funcionou, o que não funcionou, e por quê. Escrevo sobre DevOps, Kubernetes, Linux, cloud, automação, self-hosting e tudo que orbita o universo de infraestrutura e plataformas.</p>
<p>Grande parte do que aprendi veio menos de cursos e mais de produção quebrada, incidentes noturnos, documentação desatualizada e horas olhando logs no terminal. Como aprendi a estudar sozinho desde cedo, sei bem o quanto faz diferença encontrar um relato honesto de quem já passou pelo problema. Então é isso que tento trazer aqui: contexto real, não tutorial de Hello World.</p>
<p>Não escrevo como guru nem como evangelista de ferramenta. A ideia é compartilhar o que aprendi — incluindo os erros.</p>
<h2>Onde me encontrar<span class="hx:absolute hx:-mt-20" id="onde-me-encontrar"></span>
    <a href="#onde-me-encontrar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://github.com/LucasBG0"target="_blank" rel="noopener">GitHub</a></li>
<li><a href="https://www.linkedin.com/in/lucasbg0/"target="_blank" rel="noopener">LinkedIn</a></li>
</ul>
]]></content:encoded></item></channel></rss>