Heap dumps da JVM são uma fonte inestimável de informações para depurar erros de OutOfMemory e otimizar o desempenho. No entanto, eles também representam um risco potencial de vazamento de dados, pois, ao serem capturados de um ambiente de produção, contêm tudo o que o serviço estava processando no momento do snapshot: logins, senhas (às vezes em texto plano), IDs importantes, etc. – em suma, todos os tipos de dados sensíveis que não são necessários para a análise, mas que impõem sérias responsabilidades e riscos ao destinatário do dump. Como evitar isso sem prejudicar o processo de análise? Vamos investigar.
Para começar, utilizaremos um exemplo laboratorial, porém realista: o Spring PetClinic REST, a versão backend do popular aplicativo de demonstração. Esta versão, por padrão, inclui o Spring Security, que, por sua vez, gera uma senha a cada inicialização para restringir o acesso aos métodos da API. Embora a senha seja "de brincadeira" (logada em texto plano no início da aplicação), o mecanismo de como ela entra na memória e sua propagação subsequente correspondem a cenários reais com outros dados confidenciais, tornando-a adequada para nosso exemplo. Antes de buscar soluções, precisamos visualizar o problema. Felizmente, no exemplo escolhido, isso é suficiente:
- Clone o repositório:
git clone https://github.com/spring-petclinic/spring-petclinic-rest.git - Abra-o no OpenIDE ou em outro IDE.
- Abra um terminal (ou use um separado) e execute:
./mvnw spring-boot:run.
Este comando iniciará o PetClinic com as configurações padrão (com um banco de dados embutido). Em seguida, capture um heap dump e abra-o para análise, por exemplo, usando o Eclipse Memory Analyzer Tool (File -> Acquire Heap Dump…).
Entre os meio milhão de objetos, dados confidenciais podem estar em qualquer lugar. Por isso, nem o Eclipse MAT nem outras ferramentas open-source populares possuem um botão mágico "Mostre-me todos os vazamentos possíveis". No entanto, eles (especialmente MAT e VisualVM) oferecem a capacidade de executar consultas semelhantes a SQL no dump, extraindo assim os objetos mais suspeitos para verificação. A ideia principal dessa abordagem é que cada classe é representada como uma tabela, cada campo de classe como uma coluna da tabela e cada instância como uma linha. Mais detalhes sobre isso podem ser encontrados, por exemplo, em minha palestra no Joker 2024. Neste caso, com base na descrição e no código-fonte do projeto, sabemos que o Spring Security está sendo usado no modo mais simples, onde todos os dados de acesso são armazenados diretamente na memória, e as credenciais em si são representadas pela classe org.springframework.boot.autoconfigure.security.SecurityProperties.User:
javapublic static class User { /** * Default user name. */ private String name = "user"; /** * Password for the default user name. */ private String password = UUID.randomUUID().toString();
Isso é suficiente para construir uma consulta OQL como esta para o dump:
sqlSELECT toString(user.name) AS User, toString(user.password) AS Password FROM org.springframework.boot.security.autoconfigure.SecurityProperties$User user
Isso retornará o login e a senha que estavam ativos no momento da captura do dump. É exatamente esses dados que precisamos remover antes de compartilhar o dump com qualquer pessoa.
Da mesma forma, com base no conhecimento da lógica de negócios e da estrutura interna da aplicação, é possível construir várias consultas OQL para verificar pontualmente a presença de dados sensíveis no dump. Em seguida, precisamos entender como e com o que podemos limpá-los.
Método 1. Eclipse MAT.
Já que mencionamos o Memory Analyzing Tool, seria lógico esclarecer suas capacidades para remover dados confidenciais dos dumps. Essas capacidades existem, embora não estejam no local mais óbvio – é preciso clicar com o botão direito em qualquer elemento do dump e selecionar Export Snapshot:
Esta é uma funcionalidade bastante poderosa do MAT, com a qual é possível extrair diferentes fatias do dump, transformá-las de várias maneiras e salvá-las como um novo dump, como se tivesse sido capturado originalmente assim. Como se pode imaginar, a ofuscação de dados também se encaixa bem nesses trilhos. Para isso, o diálogo de exportação oferece vários parâmetros. O principal deles é redact, que suporta os seguintes valores:
NONE– não altera nada.NAMES– tenta ofuscar apenas nomes de classes, campos e métodos. Funciona em conjunto com o parâmetromap(veja abaixo). Aqui, a palavra-chave é "tenta", pois essa abordagem envolve muitas condicionalidades e limitações. Veja mais detalhes na documentação do MAT.BASIC– zera todos os arrayschar,intebyte, bem como campos de classes com tiposcharebyte. Isso permite remover a maioria das senhas de string, bem como chaves privadas, que frequentemente são representadas por objetosBigInteger. No entanto, este modo deixa intocados os campos de todos os outros tipos primitivos, bem como seus arrays, o que pode ser útil para análise e perigoso em termos de vazamentos. Se a segurança for primordial, há uma opção:FULL– zera todos os campos e arrays, e também define todos osbooleancomofalse. Ele poupa apenas referências a objetos e tamanhos de arrays para não quebrar a estrutura do dump e manter sua adequação para análise.
O segundo parâmetro relevante para nós é map, que aponta para um arquivo properties onde são registradas as correspondências entre os nomes originais das classes da aplicação e suas versões ofuscadas. Isso pode ser útil se você pretende entregar o dump para análise a um contratado externo e não quer que ele saiba o que seu aplicativo faz. O Eclipse MAT gera este arquivo automaticamente, mas se você tiver um dia de trabalho extra e quiser ter certeza de confundir um provável adversário, pode escrever essas correspondências você mesmo: para o exemplo em questão, seriam necessários apenas 77,5 mil. Isso ocorre porque o mapeamento também inclui nomes de campos estáticos, bem como classes geradas e anônimas (incluindo lambdas). Parece algo assim:
org.springframework.boot.web.server.WebServer = xod.kloblaiwaiwreen.quak.lur.thiarm.Seotreure
org.jspecify.annotations.NonNull = xod.sheopluy.pouprukliel.Trawuak
org.h2.engine.User@passwordHash = xod.gj.exiosy.Knud@tuhoucudruag
jdk.proxy3.$Proxy105@m9 = vuz.briarb.$Fliwoorm@EI
io.swagger.v3.core.util.PrimitiveType@NUMBER = ar.gioliob.is.wras.kind.Graclanaiflob@KREART
org.apache.logging.log4j.MarkerManager$$Lambda = xod.froark.pooniek.luand.Cequiayuaniats$Klourt
Para não exagerar e não quebrar o próprio parsing, o Eclipse MAT, por padrão, exclui classes dos pacotes java.* da ofuscação e permite excluir outras através do parâmetro skip (expressão regular).
Finalmente, para realizar a operação inversa, ou seja, restaurar o dump com os nomes originais das classes, você pode usar o parâmetro undo, especificamente:
- Abra o dump ofuscado no MAT.
- Selecione novamente a opção
Export Snapshot. - Especifique o caminho para salvar o dump restaurado.
- Especifique o caminho para o arquivo
propertiescom o mapeamento (ele será apenas lido). - Marque a caixa
undo(o parâmetroredactdeve permanecer emNONE).
Dessa forma, você pode não armazenar a cópia original do dump com os nomes originais das classes e, se necessário, recriá-la a partir da versão ofuscada, usando o arquivo de mapeamento como uma espécie de "chave de recuperação". Apenas é importante lembrar que este truque não serve para campos/arrays/primitivos dentro de classes, pois se eles foram ofuscados pelos modos redact=BASIC ou FULL, esse processo não tem volta...
É hora de lembrar que nossa tarefa principal é limpar a senha do Spring Security, e não criptografar todos os nomes de classes. Portanto, verificaremos a presença da senha no dump com a mesma consulta OQL após a ofuscação no modo redact=BASIC. Veja o que ele retornará:
Como pode ser visto, a segurança reina aqui agora...
No geral, não é ruim, mas é incômodo que para exportar o dump tenhamos que abrir sua versão original e fazer algo manualmente, o que significa que poderíamos ter visto dados "extras" nela, o que já acarreta riscos, responsabilidades e tudo mais. Felizmente, o Eclipse MAT suporta o chamado Batch Mode – a capacidade de executar algumas operações sem intervenção do usuário, ou seja, via CLI. O ponto de entrada deste modo é o script ParseHeapDump.[sh|bat] no diretório raiz do MAT. Como o nome sugere, o script foi originalmente projetado para parsing autônomo de dumps (grandes), mas também pode ser usado para outras tarefas, especificando seus nomes como argumentos. Para exportar um dump com ofuscação, isso pode parecer algo assim:
bash./ParseHeapDump.sh plain.hprof -output=redacted.hprof -redact=BASIC org.eclipse.mat.hprof:export
Aqui você também pode especificar outras opções do diálogo Export Heap Dump…, por exemplo, o arquivo de mapeamento de nomes de classes -map=myheapdump2.map. E se isso não for suficiente, você pode chamar tudo isso programaticamente através da Memory Analyzer API e ajustar os comportamentos necessários no nível Java.
Texto oculto Para aqueles cujas manipulações aqui apresentadas geraram mais perguntas do que compreensão, ou que em geral têm pouco conhecimento sobre dumps e sua análise no Eclipse MAT, existe um treinamento especial, onde é possível não apenas entender este tópico em teoria, mas também fixar o conhecimento na prática com uma aplicação laboratorial.
E aqui, aparentemente, temos tudo o que precisamos – configurações flexíveis de ofuscação, modo autônomo e, claro, o resultado desejado. Mas por tudo isso, temos que pagar, e a moeda aqui é memória e tempo:
- Para trabalhar com o dump, o Eclipse MAT precisa analisá-lo, e para isso ele precisa, em média, de tanta memória RAM quanto o próprio dump pesa; você pode ler sobre como analisar dumps grandes com pouca RAM nesta nota.
- Durante o parsing, o MAT cria muitos índices auxiliares, cujo tamanho pode exceder em várias vezes o tamanho do dump original, portanto, é necessário muito espaço em disco.
- Ao mesmo tempo, o dump original não pode ser excluído após o parsing – ele ainda permanece parte da estrutura geral de dados.
- O parsing consiste em muitas etapas, incluindo múltiplas varreduras do dump original, portanto, em grandes volumes, isso pode levar muito tempo.
- O autor destas linhas teve um período de otimização de um servidor (64 GB RAM), quando só era possível fazer correções não mais que uma vez por dia, porque cada novo dump levava de 4 a 5 horas para ser analisado (durante a noite).
E o próprio Eclipse MAT é um pacote completo: sua distribuição pesa cerca de 100 MB, então se a tarefa for incluí-lo como parte do CI/CD como uma etapa automática de ofuscação de dumps obtidos de um servidor de produção, então esta ferramenta não é a melhor escolha.
Felizmente, existem alternativas.
Método 2. Heap Dump Tool.
A PayPal, trabalhando de perto com dados de pagamento e tendo parte de suas aplicações em JVM, provavelmente se deparou muitas vezes com a necessidade de analisar heap dumps "perigosos", por isso desenvolveu uma ferramenta especial para isso, o heap-dump-tool, e a disponibilizou em open source. Esta é uma ferramenta CLI para uma tarefa específica – você fornece o dump original como entrada e obtém um "desinfetado" como saída:
bash$ java -jar heap-dump-tool.jar sanitize leak.hprof sanitized.hprof INFO Application - heap-dump-tool (1.3.4 ca0325a, 2025-11-29T02:48:26.000-0800) INFO SanitizeCommandProcessor - Pre-processing ... INFO SanitizeCommandProcessor - String fields to exclude from sanitization: java.lang.Thread#name,java.lang.ThreadGroup#name INFO SanitizeCommandProcessor - Force match String.coder: true INFO SanitizeCommandProcessor - Input File: leak.hprof INFO SanitizeCommandProcessor - Starting heap dump sanitization ... INFO SanitizeCommandProcessor - Input File: leak.hprof INFO SanitizeCommandProcessor - Output File: sanitized.hprof INFO SanitizeCommandProcessor - Finished heap dump sanitization in 13s
Se executarmos a mesma consulta OQL para detectar a senha no dump resultante, ela produzirá aproximadamente o seguinte:
Ou seja, segurança total.
No entanto, isso não é toda a história – na verdade, por padrão, o Heap Dump Tool limpa apenas arrays de bytes e caracteres, mantendo intactos valores inteiros, de ponto flutuante e outros (incluindo arrays). Isso pode deixar uma brecha para vazamento se, por exemplo, um campo inteiro contiver um ID de cliente pessoal ou o saldo de sua conta (em alguns lugares, há a prática de armazenar somas de dinheiro nas menores unidades da moeda). Para tais casos, ao chamar a utilidade, você precisa adicionar o flag --sanitize-byte-char-arrays-only=false (ou abreviadamente -s=false), e então todos os primitivos e seus arrays irão "para a faca".
Este modo não está ativado por padrão, provavelmente porque pode complicar a análise posterior. Vamos analisar um exemplo. Suponha que precisemos correlacionar os dados do dump com métricas do SO ou outra telemetria externa. Uma das principais "pontes" para isso seria o PID do processo. E embora ele possa ser obtido de várias maneiras, seria mais conveniente obtê-lo diretamente do dump – no caso do Spring Boot, basta executar uma consulta OQL como esta:
sqlSELECT OBJECTS s.source FROM org.springframework.boot.ApplicationInfoPropertySource s
Para o dump original ("cru"), ele retornará um HashMap com conteúdo aproximado:
No entanto, após a ofuscação, este resultado, obviamente, mudará. E se spring.application.version não puder ser salvo em qualquer caso (por ser um valor de string), a integridade de spring.application.pid depende inteiramente do flag --sanitize-byte-char-arrays-only:
- Com o valor
true(padrão), o PID será preservado. - Com
false, o conteúdo do mapa será aproximadamente:
Ou seja, extremamente seguro.
Se o PID fosse gravado em um campo separado de alguma classe, ele ainda poderia ser salvo com a opção --exclude-string-fields (abreviadamente -e), que informa ao ofuscador quais campos de quais classes devem permanecer intactos. Por padrão, esses campos são apenas nomes de threads e seus grupos (veja o trecho do log acima).
Um leitor particularmente chato ou atento pode ter a pergunta: "Por que especificar o comando sanitize se a ferramenta foi criada para isso?" O fato é que ela não só processa dumps prontos, mas também os captura, inclusive de aplicações em contêineres (inclusive quando ela mesma está rodando em um contêiner). Para isso, é preciso mudar o comando sanitize para capture, e o caminho para o dump – para o nome do contêiner de destino. Neste caso, o dump capturado ficará dentro do contêiner de destino e precisará ser retirado de lá manualmente (se você conseguir antes que algum Kubernetes o mate).
Mesmo sem se aprofundar nas outras funcionalidades desta ferramenta, já deve ficar claro que ela é bem adequada para nossa tarefa:
- Limpa senhas do dump mesmo com configurações padrão.
- Permite influenciar o grau de "desinfecção".
- Não requer parsing do dump.
- Vem em um único arquivo JAR (feito em Spring Boot + PicoCLI).
- Integra-se bem ao CI/CD devido à interface CLI.
Mas onde estão os contras:
- Em dumps enormes, a ferramenta pode exigir esforço: internamente, ela usa um
BufferedInputStreamcomum com um tamanho de buffer de 100 MB. E se em dumps pequenos isso for suficiente, à medida que seus tamanhos aumentam, você pode se deparar com uma escolha desagradável: ou esperar mais tempo para lê-los/escrevê-los, ou alocar muito mais memória para o buffer. - Em casos particularmente difíceis ou específicos, pode ser necessária uma lógica de ofuscação personalizada, mas o
heap-dump-toolnão oferece a possibilidade de modificá-la, e fazer um fork dele para isso é uma empreitada duvidosa.
É improvável que muitas pessoas enfrentem esses problemas, mas se por acaso acontecer, aqui está uma opção para esse caso também:
Método 3. Hprof-redact
O conhecido especialista em JVM em círculos restritos, Johannes Bechberger, é o autor de muitas utilidades úteis para análise e otimização de desempenho. E recentemente, a pedido de outro especialista conhecido no mundo Java, Volker Simonis, ele lançou o hprof-redact – uma ferramenta para ofuscar dumps de memória antes de enviá-los para análise.
A ferramenta é em muitos aspectos semelhante ao heap-dump-tool descrito acima:
- Também é CLI.
- Também vem em um único arquivo JAR.
- Também oferece diferentes modos de edição de dados.
Mas há diferenças significativas:
- De acordo com o autor, é apenas um protótipo (proof-of-concept) por enquanto, não uma ferramenta completa.
- Lê dumps em modo de streaming:
- Vantagens: não requer ajuste de buffer para dumps grandes.
- Desvantagens: faz duas passagens completas pelo dump, portanto, ainda não é instantâneo.
- Controla a edição através dos chamados
transformers, dos quais, na versão 0.2.1 (fevereiro de 2026), havia três:zero(padrão) – zera todos os primitivos e substitui o conteúdo das strings por zeros.zero-strings– também substitui o conteúdo das strings por zeros, mas deixa os primitivos intactos.drop-strings– torna todas as strings vazias (comprimento zero), mas deixa os primitivos intactos.
A presença desses transformers, aliás, é a principal diferença de design do hprof-redact em relação ao heap-dump-tool mencionado acima, pois um transformer é parte da API pública da ferramenta, o que permite, se necessário, escrever sua própria lógica de ofuscação implementando a interface HprofTransformer, extensa, mas totalmente opcional.
Separadamente para o hprof-redact, vale mencionar a compressão do dump durante o processamento. Ela existe em outras ferramentas também, mas o Eclipse MAT e o heap-dump-tool a implementam com GZip comum, enquanto o hprof-redact tem uma abordagem diferente – ele esvazia strings e arrays primitivos, mantendo as informações sobre seu comprimento original. Nesse sentido, tal compressão não é totalmente honesta, pois perde dados sem possibilidade de recuperação. No entanto, seu objetivo é outro – reduzir o tamanho do dump, mantendo sua identidade estrutural, mas não de conteúdo.
Ao contrário do heap-dump-tool, a utilidade hprof-redact por padrão limpa não apenas strings, mas também primitivos (incluindo seus arrays), portanto, ambas as consultas OQL acima (para detectar a senha e obter o PID + versão da aplicação) retornarão um grande... nada. Tudo para a alegria do departamento de TI.
No geral, o hprof-redact ainda não pode ser considerado uma solução completa, pois foi criado muito recentemente e ainda oferece poucas funcionalidades. No entanto, pode se tornar uma alternativa digna ao heap-dump-tool em casos onde uma lógica de ofuscação de dados específica no dump é necessária e você está disposto a implementá-la programaticamente.
Método 4. JDK (não)
Antes de considerar quaisquer ferramentas de terceiros, seria mais lógico perguntar se há algo pronto na biblioteca padrão. A resposta é: atualmente não, mas as discussões estão em andamento. No JIRA do projeto OpenJDK, há um ticket JDK-8337517 que propõe aprimorar a utilidade jcmd e adicionar uma nova opção JVM -XX:+HeapDumpRedacted para que ambas produzam um dump desinfetado com primitivos zerados. Há também um Pull Request correspondente no Github para este ticket, mas ele está no status Closed, e o próprio ticket, embora tenha o status Open, tem o campo Fix Version(s) com o valor tbd, ou seja, "algum dia".
As principais razões para essa situação foram a baixa prioridade da tarefa e a falta (ainda) de uma visão unificada entre desenvolvedores e revisores sobre como essa funcionalidade deve ser implementada: o que exatamente deve ser limpo, quais devem ser os argumentos de chamada e como proceder quando, se a JVM produzir um dump ofuscado, não haverá onde obter o original.
Nesta discussão participa também o nosso conhecido Alexei Shipilev, comentando em seu estilo característico:
... while most of the time the confidential data is in primitive arrays (key material, cipher buffers, string contents), primitive fields carry identifiable data as well, e.g. numeric account/transaction IDs. Even double/floats contain data often, think financial data or even (pants heavily) LLM weights.
O hprof-redact é uma tentativa de implementar essa funcionalidade de acordo com a ideia original (do autor). O quão (in)bem-sucedida ela foi – cabe a nós, seus usuários, julgar, portanto, se você tem experiência em usá-la e opiniões sobre ela, não hesite em compartilhá-las, por exemplo, na página do desenvolvedor ou através do GitHub issues.
Conclusão
Existem poucas ferramentas gratuitas para desinfetar dumps antes de sua análise. E as principais entre elas são apenas duas:
- Eclipse MAT – poderoso, mas pesado; requer muitos ajustes, mas oferece máxima liberdade de ação (GUI / CLI / API).
- heap-dump-tool – não é rico, mas leve; integra-se perfeitamente em pipelines; não faz tudo, mas faz muito (CLI / API).
Separadamente delas, está a ferramenta hprof-redact, muito nova e (ainda) não profundamente desenvolvida, em muitos aspectos semelhante ao heap-dump-tool, mas apostando na flexibilidade da API Java e no suporte a dumps grandes.
Uma comparação um pouco mais formal dessas ferramentas, em grande parte baseada em avaliações subjetivas do autor, é apresentada nesta tabela:
| Característica \ Ferramenta | Eclipse MAT | Heap Dump Tool | Hprof-redact |
|---|---|---|---|
| Trabalho com dumps grandes | 👍 | 👍👍 | 👍👍👍 |
| Suporte a contêineres Docker | ❌ | ✅ | ❌ |
| Flexibilidade de configurações de ofuscação | 👍👍👍 | 👍 | 👍👍 |
| Compressão durante ofuscação | ✅ (zip) | ✅ (zip) | ✅ (hprof) |
| Flexibilidade de API | 👍👍👍 | 👍 | 👍👍 |
| Carregamento de dumps no formato TAR | ❌ | ✅ | ❌ |
| Adequação para CI/CD | 👍 | 👍👍 | 👍 |
Se você já resolveu essa tarefa usando essas ou outras ferramentas, por favor, compartilhe sua experiência nos comentários – sua experiência será um ótimo complemento a este artigo.
Junte-se à comunidade de desenvolvedores de Spring Boot em russo no Telegram – Spring АйО, para se manter atualizado sobre as últimas notícias do mundo do desenvolvimento em Spring Boot e tudo relacionado a ele.





