Mapeando a Memória de um Programa Go no Linux

Neste post, vamos explorar como mapear e entender a memória de programas Go 1.27 no Linux. Com o crescimento de aplicações Go em produção, saber o que está acontecendo na memória é essencial para otimizar performance e debugar problemas. Vou mostrar as ferramentas que surgiram para facilitar essa tarefa.

O que significa mapear a memória?

Mapear a memória é entender como seu programa Go está usando a RAM do sistema. Não é só sobre quanto de memória está sendo usado, mas onde essa memória está sendo alocada, qual estrutura de dados está consumindo mais bytes e se há vazamentos de memória.

No Linux, você pode ver essa informação de várias formas. As mais comuns são:

  • pprof — o profiler integrado do Go que mostra allocations e heap
  • /proc/maps — arquivo do kernel que lista todas as regiões de memória do processo
  • runtime.MemStats — estatísticas do runtime do Go em tempo real

Por que isso importa no Go 1.27?

O Go 1.27 trouxe melhorias significativas no runtime e no garbage collector. Essas mudanças tornaram mais fácil (e necessário) entender como a memória está sendo distribuída. Se você está migrando de versões antigas, provavelmente quer verificar se o comportamento de memória mudou.

Começando com pprof

O pprof é a forma mais direta de explorar a memória do seu programa Go. Aqui está um exemplo básico:

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"time"
)

func main() {
	// Iniciar servidor pprof em background
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	// Seu código aqui
	allocateMemory()
	time.Sleep(30 * time.Second)
}

func allocateMemory() {
	var data []int
	for i := 0; i < 1000000; i++ {
		data = append(data, i)
	}
}

Com isso rodando, você acessa as estatísticas em:

go tool pprof http://localhost:6060/debug/pprof/heap

Dentro do pprof, use top para ver os maiores consumidores de memória:

(pprof) top
Showing nodes accounting for 6809.24kB, 100% of 6809.24kB total
Showing top 10 nodes out of 21
      flat  flat%   sum%        cum   cum%
 5784.23kB 84.95% 84.95%  5784.23kB 84.95%  main.allocateMemory (inline)
 1025.01kB 15.05%   100%  1025.01kB 15.05%  runtime.mallocgc
         0     0%   100%  5784.23kB 84.95%  main.main
         0     0%   100%   512.01kB  7.52%  runtime.(*scavengerState).sleep
         0     0%   100%   512.01kB  7.52%  runtime.(*timer).maybeAdd
         0     0%   100%   512.01kB  7.52%  runtime.(*timer).modify
         0     0%   100%   512.01kB  7.52%  runtime.(*timer).reset (inline)
         0     0%   100%   512.01kB  7.52%  runtime.(*timers).addHeap
         0     0%   100%      513kB  7.53%  runtime.allocm
         0     0%   100%   512.01kB  7.52%  runtime.bgscavenge

Usando /proc/maps para visão do sistema

Se você quer entender como o Linux vê a memória do seu programa, cat /proc/<PID>/maps mostra todas as regiões de memória alocadas:

cat /proc/$(pgrep seu_programa)/maps

Você verá algo como:

7f1234567000-7f1234678000 r-xp 00000000 ca:02 1234567 /path/to/go/binary
7f1234678000-7f1234789000 r--p 00111000 ca:02 1234567 /path/to/go/binary
...

Cada linha representa um segmento de memória com permissões (r=read, w=write, x=execute) e o arquivo mapeado. Isso é útil para detectar se você tem muitas alocações fragmentadas.

Melhorias do runtime em Go 1.27

Go 1.27 introduziu ajustes no GOGC e na forma como o garbage collector aloca memória. Se você está debugando crescimento inesperado de memória, considere:

import "runtime/debug"

func init() {
	// Ajustar comportamento do GC
	debug.SetGCPercent(50) // Mais agressivo
	debug.SetMaxStack(1000000) // Limite de goroutines
}

Monitorar essas métricas com runtime.ReadMemStats():

var m runtime.MemStats
runtime.ReadMemStats(&m)

log.Printf("Alloc: %v MB, TotalAlloc: %v MB, Sys: %v MB", 
	m.Alloc/1024/1024, 
	m.TotalAlloc/1024/1024, 
	m.Sys/1024/1024)

Na prática: um pipeline de profiling

Para investigar vazamentos de memória em produção, siga esse fluxo:

  1. Colete um heap dump quando a memória estiver alta: curl http://localhost:6060/debug/pprof/heap > heap.prof
  2. Analise localmente: go tool pprof heap.prof (pprof) list allocateMemory
  3. Compare com um baseline anterior para identificar o culpado
  4. Use /proc/maps para validar que o aumento não é fragmentação do sistema

Conclusão

Mapear memória em Go não é complicado com as ferramentas certas. O trio pprof + /proc/maps + runtime.MemStats cobre 99% dos casos de análise. Go 1.27 melhorou o runtime, mas isso também significa que você tem mais controle — use isso a seu favor.

Comece com pprof, entenda o padrão de alocação do seu código e depois refine com runtime.MemStats quando necessário.

Gostou do conteúdo?

  • Inscreva-se na newsletter para receber mais dicas práticas sobre Go diretamente no seu e-mail!
  • 🚀 Conheça a Imersão Golang e leve seus conhecimentos em Go para o próximo nível!

Faça parte da comunidade!

Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

* indicates required

Deixe uma resposta