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.
1 2 3 4 5 |
0:000> !EEVersion 4.700.20.26901 (3.x runtime) free 4,700,20,26901 @Commit: 018cfd06dceb19b6eb1e9217a500fb1071946fcd Workstation mode SOS Version: 3.0.1.2901 retail build |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
0:000> !EEHeap -gc Number of GC Heaps: 1 generation 0 starts at 0x00000189DA42F8C8 generation 1 starts at 0x00000189D9CAEFB0 generation 2 starts at 0x00000189D8921000 ephemeral segment allocation context: none segment begin allocated size 00000189D8920000 00000189D8921000 00000189DA45A7C0 0x1b397c0(28547008) Large object heap starts at 0x00000189E8921000 segment begin allocated size 00000189E8920000 00000189E8921000 00000189E8925CB8 0x4cb8(19640) Total Size: Size: 0x1b3e478 (28566648) bytes. ------------------------------ GC Heap Size: Size: 0x1b3e478 (28566648) bytes. |
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
1 2 3 4 5 |
0:000> !EEVersion 4.700.20.26901 (3.x runtime) free 4,700,20,26901 @Commit: 018cfd06dceb19b6eb1e9217a500fb1071946fcd Server mode with 4 gc heaps SOS Version: 3.0.1.2901 retail build |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
0:000> !EEHeap -gc Number of GC Heaps: 4 ------------------------------ Heap 0 (0000025579066980) generation 0 starts at 0x000002557ABFFEE0 generation 1 starts at 0x000002557AA21018 generation 2 starts at 0x000002557AA21000 ephemeral segment allocation context: none segment begin allocated size 000002557AA20000 000002557AA21000 000002557B9B7830 0xf96830(16345136) Large object heap starts at 0x000002597AA21000 segment begin allocated size 000002597AA20000 000002597AA21000 000002597AA21018 0x18(24) Heap Size: Size: 0xf96848 (16345160) bytes. ------------------------------ Heap 1 (0000025579069B70) generation 0 starts at 0x000002567ABF9B70 generation 1 starts at 0x000002567AA21018 generation 2 starts at 0x000002567AA21000 ephemeral segment allocation context: none segment begin allocated size 000002567AA20000 000002567AA21000 000002567B98CB98 0xf6bb98(16169880) Large object heap starts at 0x000002598AA21000 segment begin allocated size 000002598AA20000 000002598AA21000 000002598AA462E8 0x252e8(152296) Heap Size: Size: 0xf90e80 (16322176) bytes. ------------------------------ Heap 2 (0000025579108140) generation 0 starts at 0x000002577ABA9BF8 generation 1 starts at 0x000002577AA21018 generation 2 starts at 0x000002577AA21000 ephemeral segment allocation context: none segment begin allocated size 000002577AA20000 000002577AA21000 000002577B9488F0 0xf278f0(15890672) Large object heap starts at 0x000002599AA21000 segment begin allocated size 000002599AA20000 000002599AA21000 000002599AA21850 0x850(2128) Heap Size: Size: 0xf28140 (15892800) bytes. ------------------------------ Heap 3 (00000259C515FE90) generation 0 starts at 0x000002587ABE2670 generation 1 starts at 0x000002587AA21018 generation 2 starts at 0x000002587AA21000 ephemeral segment allocation context: none segment begin allocated size 000002587AA20000 000002587AA21000 000002587B94B958 0xf2a958(15903064) Large object heap starts at 0x00000259AAA21000 segment begin allocated size 00000259AAA20000 00000259AAA21000 00000259AAA21018 0x18(24) Heap Size: Size: 0xf2a970 (15903088) bytes. ------------------------------ GC Heap Size: Size: 0x3d7a178 (64463224) bytes. |
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 camporesources.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 ouresources.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
comotrue
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
comotrue
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
oudotnet-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
oudotnet-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.