Tiago Tartari

Conteúdo

Garbage Collection no .NET – Modo Server e Workstation. Como, quando e qual utilizar?

O Garbage Collection (GC) é um componente do runtime do .NET, responsável por gerenciar a alocação e a liberação de memória dos objetos criados pelo seu código. O GC opera automaticamente, eliminando a necessidade de liberação manual de memória. Contudo, é relevante compreender como o GC funciona e como ele pode ser configurado para adequar-se às necessidades específicas do seu cenário.

Workstation vs. Server

O Common Language Runtime (CLR) oferece duas configurações distintas para o Garbage Collection: Workstation e Server. Para escolher entre eles, você pode alterar a configuração System.GC.Server no arquivo runtimeconfig.json ou através da variável de ambiente DOTNET_gcServer. O valor padrão é false, indicando o uso do GC em modo Workstation. Caso deseje utilizar o GC em modo Server, defina o valor para true.

Workstation

O modo Workstation é o padrão do GC para aplicativos autônomos, isto é, que NÃO são hospedados por outro processo, como o ASP.NET. Este modo é ideal para aplicativos onde a interatividade do usuário exige uma resposta rápida, buscando oferecer pausas o mais curtas possível. Existem duas variantes no modo Workstation: simultâneo e não simultâneo. No modo simultâneo, também conhecido como background, o GC realiza a coleta de lixo em um thread separado, permitindo que os threads gerenciados prossigam com suas operações durante a coleta. Já no modo não simultâneo, o GC executa a coleta de lixo no mesmo thread que iniciou a coleta, suspendendo todos os outros threads gerenciados durante o processo. Este modo é adotado automaticamente em computadores que possuem apenas uma CPU lógica, independentemente da configuração.

Análise do modo Workstation com WinDbg

O código a seguir é uma saída dos comandos !EEVersion e !EEHeap -gc do SOS, uma extensão de depuração para o .NET. Estes comandos são empregados para coletar informações sobre a versão do runtime e o heap gerenciado do .NET.

O comando !EEVersion fornece detalhes sobre o runtime e indica o modo de GC configurado, neste caso, workstation.

Detalhamento do Heap com !EEHeap -gc

O comando !EEHeap -gc exibe informações sobre a memória do processo utilizada pelo GC. Ele mostra o número de heaps, o início de cada geração, o contexto de alocação do segmento efêmero, os segmentos do heap gerenciado e o Large Object Heap (LOH). Além disso, ele detalha o tamanho total e o tamanho do heap do GC em bytes.

Neste exemplo, temos um heap dividido em três gerações: 0, 1 e 2. A geração 0 é a mais recente e frequentemente coletada, enquanto a geração 2 é a mais antiga e menos coletada. O segmento efêmero, que contém os objetos das gerações 0 e 1, não tem um contexto de alocação ativo, indicando que um novo segmento será criado na próxima alocação.

Existem dois segmentos no heap gerenciado, e um no LOH, cada um com seu endereço de início, endereço de alocação e tamanho em bytes. O tamanho total do heap é a soma dos tamanhos de todos os segmentos, tanto do heap gerenciado quanto do LOH.

Uso prático do modo Workstation

Um exemplo prático do uso do modo workstation é em aplicativos de edição de imagens, onde a alta performance e baixa latência são necessárias para satisfazer as exigências do usuário. O modo workstation simultâneo, ou background, é particularmente útil, pois permite que o aplicativo continue operando enquanto o GC coleta os objetos não utilizados, minimizando pausas perceptíveis para o usuário.

Modo Server

O modo Server é a configuração do GC para aplicativos de servidor que necessitam de alta taxa de transferência e escalabilidade. Esse modo é adotado por padrão em aplicativos hospedados, como os desenvolvidos em ASP.NET. Assim como o modo Workstation, o modo Server pode operar de forma simultânea ou não simultânea. Sua principal característica é a utilização de um heap e um thread dedicado para cada CPU lógica, permitindo a coleta de todos os heaps simultaneamente. Esse paralelismo e concorrência possibilitam uma coleta de lixo mais rápida e eficiente, sem competição com os threads do usuário. Além disso, o modo Server tende a ter segmentos de memória maiores, reduzindo a fragmentação do heap.

Análise do modo Server com WinDbg

Esta saída confirma que o GC está operando no modo Server, com 4 heaps. Isso indica o uso de um heap e um thread dedicado para cada CPU lógica, melhorando a performance e a escalabilidade em aplicações como ASP.NET ou WebApi.

Detalhamento dos Heaps

Cada heap tem três gerações: 0, 1 e 2. A geração 0 é a mais recente e a mais frequentemente coletada, e a geração 2 é a mais antiga e a menos frequentemente coletada. O segmento efêmero é o segmento atual de alocação, que contém os objetos da geração 0 e 1. O contexto de alocação é o ponteiro que indica onde o próximo objeto será alocado. Neste caso, o contexto de alocação é nenhum, o que significa que o segmento efêmero está cheio e um novo segmento será criado na próxima alocação.

Os heaps incluem dois tipos de segmentos: o segmento do heap gerenciado para objetos menores que 85kb e o segmento do Large Object Heap (LOH) para objetos maiores. Cada segmento tem um endereço de início, um endereço de alocação e um tamanho em bytes.

Tamanho e Performance dos Heaps

O tamanho de cada heap e o tamanho total do heap do GC são detalhados, com o tamanho total no exemplo sendo 0x3d7a178 (64.463.224 bytes). Os segmentos no modo Server são geralmente maiores que no modo Workstation, com o GC solicitando grandes blocos de memória para reduzir a fragmentação. O tamanho dos segmentos varia de acordo com o número de CPUs lógicas e a arquitetura do sistema (32-bit ou 64-bit).

Uso prático do modo Server

O modo Server aumenta a taxa de transferência e a utilização da CPU em aplicações de servidor com muitas alocações e requisições. Cada thread gerencia um heap independente, aumentando a eficiência das coletas. Um exemplo prático é um aplicativo web com alto volume de requisições, onde o modo Server simultâneo pode melhorar a performance, permitindo o processamento de requisições enquanto o GC opera, sem causar pausas significativas.

Garbage Collection em ambiente Clusterizado

O Garbage Collection (GC) mantém o mesmo funcionamento, independentemente da utilização de contêineres ou não. As configurações do tipo de GC também permanecem inalteradas. No entanto, em ambientes clusterizados, como o Kubernetes, surgem desafios e recomendações específicas para otimizar o uso de memória e prevenir problemas de desempenho ou estabilidade.

A escolha entre os modos de GC – Workstation ou Server – depende do tipo de serviço executado no contêiner. O modo Workstation, sendo o padrão, é sugerido para aplicações com menor carga de trabalho ou que demandam baixa latência. Por outro lado, o modo Server é mais apropriado para aplicações com alta demanda de memória ou que requerem uma elevada taxa de transferência.

Para configurar o modo Server em contêineres, é necessário definir a variável de ambiente DOTNET_gcServer como true. Esta configuração assegura que o GC opere de maneira otimizada para as necessidades específicas de aplicações em ambientes clusterizados, levando em conta as características únicas desses cenários.

Problemas de versões anteriores ao .NET 5

Nas versões anteriores do .NET Core, um problema recorrente era a alocação excessiva de memória em contêineres. O Garbage Collection (GC) não era capaz de detectar os limites de memória impostos pelo orquestrador, como o Kubernetes. Esse descompasso podia levar a várias complicações, incluindo pressão de memória, swapping, e, em casos mais extremos, o encerramento do contêiner pelo próprio orquestrador.

Este problema foi efetivamente resolvido com o lançamento do .NET Core 3.1. A atualização trouxe uma nova abordagem para o gerenciamento de memória em ambientes de contêineres, introduzindo os conceitos de limites de memória rígidos (hard limit) e flexíveis (soft limit). O GC, a partir dessa versão, passou a utilizar esses limites para otimizar a sua alocação de memória, adaptando-se mais precisamente ao espaço disponível no contêiner.

Recomendações para configurar o GC

Algumas recomendações para configurar o GC em ambiente clusterizado são:

  • É importante definir o limite de memória rígido do contêiner em consonância com as configurações do orquestrador. Isso pode ser feito usando a opção --memory no Docker ou o campo resources.limits.memory no Kubernetes. Essa prática assegura que o contêiner não exceda o uso de memória alocado, evitando problemas de estabilidade.
  • Configurar o limite de memória flexível do contêiner ajuda a reservar uma margem de memória para o Garbage Collection (GC) e outros processos do sistema. Utilize a opção --memory-reservation no Docker ou resources.requests.memory no Kubernetes para esta configuração.
  • Para aplicações com alta demanda de memória ou que necessitam de alta taxa de transferência, recomenda-se usar o modo Server de GC. Defina a variável de ambiente DOTNET_gcServer como true no contêiner para ativar este modo.
  • Aplicações que requerem baixa latência se beneficiam do modo Concurrent de GC. Defina a variável de ambiente DOTNET_gcConcurrent como true no contêiner para habilitar esta configuração.
  • Para manter um controle eficaz sobre o uso de memória e o comportamento do GC, utilize ferramentas como dotnet-counters, dotnet-trace ou dotnet-dump.

Erros comuns no uso do GC em ambientes clusterizados

Alguns erros mais comuns que podem ocorrer ao usar o GC em ambiente clusterizado são:

  • Um dos problemas mais comuns é a alocação excessiva de memória, que pode levar a pressão de memória, swapping ou até mesmo ao encerramento do contêiner pelo orquestrador. Para evitar isso, é crucial definir adequadamente os limites de memória rígidos (hard limits) e flexíveis (soft limits) do contêiner. Além disso, escolher o modo de GC apropriado para a aplicação pode ajudar a controlar o uso de memória eficientemente.
  • A fragmentação de memória é outro desafio, que pode diminuir a eficiência do GC e prolongar as pausas de coleta. Uma forma de mitigar esse problema é através do uso do modo Server de GC. Este modo utiliza segmentos de memória maiores e com menor fragmentação, otimizando a gestão de memória em ambientes que exigem alto desempenho.
  • Vazamentos de memória ocorrem quando objetos não são corretamente liberados pelo GC, geralmente devido a referências persistentes de outros objetos. Ferramentas como dotnet-dump ou dotnet-gcdump são essenciais para identificar esses vazamentos. Elas permitem analisar o heap da aplicação, destacando objetos que consomem quantidades excessivas de memória e auxiliando no diagnóstico de problemas de alocação.

Conclusão

O conhecimento profundo sobre o funcionamento e configuração do GC é essencial para otimizar o desempenho e a eficiência de aplicações .NET, especialmente em ambientes complexos como contêineres e clusters.

A escolha entre os modos Workstation e Server do GC deve ser baseada nas características específicas da aplicação, como a necessidade de baixa latência ou alta taxa de transferência. No ambiente de contêineres, essa escolha é ainda mais crucial devido às restrições de recursos e à natureza distribuída dos sistemas.

As versões mais recentes do .NET Core, a partir da 3.1, trouxeram melhorias significativas no gerenciamento de memória para contêineres, introduzindo os conceitos de limites de memória rígidos e flexíveis. Isso permitiu uma alocação de memória mais eficaz e a prevenção de problemas como alocação excessiva e fragmentação de memória.

Ferramentas como dotnet-dump, dotnet-gcdump, dotnet-counters, entre outras, são fundamentais para monitorar e diagnosticar problemas de memória, incluindo vazamentos de memória. Essas ferramentas proporcionam uma visão detalhada do comportamento do GC e do uso de memória, possibilitando ajustes e melhorias contínuas.

Em ambientes clusterizados, como o Kubernetes, as configurações do GC devem ser cuidadosamente adaptadas para lidar com os desafios específicos desses ambientes, como limitações de recursos e orquestração de contêineres.

FAQ: Perguntas Frequentes

1. O que diferencia os modos Workstation e Server no Garbage Collection do .NET?

O modo Workstation é otimizado para aplicações com menor carga de trabalho e que necessitam de baixa latência, enquanto o modo Server é ideal para aplicações que exigem alta taxa de transferência e escalabilidade, usando um heap e um thread dedicados para cada CPU lógica.

2. Como a versão 3.1 do .NET Core melhorou o gerenciamento de memória em contêineres?

A versão 3.1 introduziu limites de memória rígidos e flexíveis, permitindo que o GC ajuste melhor a alocação de memória ao espaço disponível no contêiner, evitando problemas como alocação excessiva e fragmentação.

3. Quais são as ferramentas recomendadas para diagnosticar problemas de memória no .NET?

Ferramentas como dotnet-dump, dotnet-gcdump, dotnet-counters, e dotnet-trace são recomendadas para monitorar o uso de memória e identificar problemas como vazamentos de memória e ineficiências na coleta de lixo.

4. Quando devo usar o modo Concurrent de GC em minha aplicação .NET?

O modo Concurrent de GC é recomendado para aplicações que precisam manter baixa latência, pois permite que a coleta de lixo ocorra em paralelo com a execução da aplicação, minimizando pausas.

5. Qual é a importância de definir os limites de memória em ambientes clusterizados como Kubernetes?

Definir os limites de memória é crucial para garantir que a aplicação não exceda o uso de memória alocado e para manter a estabilidade do sistema. Isso é particularmente importante em ambientes clusterizados, onde os recursos são compartilhados e gerenciados de forma mais rígida.

Compartilhe:

Tiago Tartari

Tiago Tartari

Eu ajudo e capacito pessoas e organizações a transformar problemas complexos em soluções práticas usando a tecnologia para atingir resultados extraordinários.

Qual é o desafio
que você tem hoje?