Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo descreve problemas relacionados à coleta de lixo e ao uso de memória. Ele aborda problemas relacionados a heap gerenciado e explica como minimizar o efeito da coleta de lixo em seus aplicativos. Cada problema tem links para procedimentos que você pode usar para investigar problemas.
Ferramentas de análise de desempenho
As seções a seguir descrevem as ferramentas disponíveis para investigar problemas de uso de memória e coleta de lixo. Os procedimentos fornecidos posteriormente neste artigo referem-se a essas ferramentas.
Contadores de desempenho de memória
Você pode usar contadores de desempenho para coletar dados de desempenho. Para obter instruções, consulte Profiling de Tempo de Execução. A categoria memória CLR do .NET de contadores de desempenho, conforme descrito em Contadores de Desempenho no .NET, fornece informações sobre o coletor de lixo.
Depuração com SOS
Você pode usar o Depurador do Windows (WinDbg) para inspecionar objetos no heap gerenciado.
Para instalar o WinDbg, instale as Ferramentas de Depuração para Windows na página Página de Download das Ferramentas de Depuração para Windows.
Eventos ETW de coleta de lixo
O ETW (rastreamento de eventos para Windows) é um sistema de rastreamento que complementa o suporte de criação de perfil e depuração fornecido pelo .NET. A partir do .NET Framework 4, os eventos ETW de coleta de lixo capturam informações úteis para analisar o heap gerenciado de um ponto de vista estatístico. Por exemplo, o GCStart_V1 evento, que é gerado quando uma coleta de lixo está prestes a ocorrer, fornece as seguintes informações:
- Qual geração de objetos está sendo coletada.
- O que disparou a coleta de lixo.
- Tipo de coleta de lixo (simultânea ou não simultânea).
O registro em log de eventos ETW é eficiente e não mascarará nenhum problema de desempenho associado à coleta de lixo. Um processo pode fornecer seus próprios eventos junto com eventos ETW. Quando registrados em log, tanto os eventos de coleta de lixo quanto os eventos do aplicativo podem ser correlacionados para determinar como e quando os problemas de heap ocorrem. Por exemplo, um aplicativo de servidor pode fornecer eventos no início e no final de uma solicitação de cliente.
A API de Criação de Perfil
As interfaces de criação de perfil clr (common language runtime) fornecem informações detalhadas sobre os objetos que foram afetados durante a coleta de lixo. Um criador de perfil pode ser notificado sobre quando uma coleta de lixo começa e termina. Ele pode fornecer relatórios sobre os objetos no heap gerenciado, incluindo uma identificação de objetos em cada geração. Para obter mais informações, consulte Visão geral do perfil.
Os profilers podem fornecer informações abrangentes. No entanto, profilers complexos podem potencialmente modificar o comportamento de um aplicativo.
Monitoramento de recursos do domínio do aplicativo
A partir do .NET Framework 4, o ARM (monitoramento de recursos de domínio de aplicativo) permite que os hosts monitorem o uso da CPU e da memória pelo domínio do aplicativo. Para obter mais informações, consulte o Monitoramento de Recursos do Domínio do Aplicativo.
Solucionar problemas de desempenho
A primeira etapa é determinar se o problema é, na verdade, coleta de lixo. Se você determinar que é o caso, selecione uma opção da lista a seguir para solucionar o problema.
- Uma exceção fora de memória é gerada
- O processo usa muita memória
- O coletor de lixo não recupera objetos rápido o suficiente
- O heap gerenciado está fragmentado demais
- Pausas na coleta de lixo são muito longas
- A geração 0 é muito grande
- O uso da CPU durante uma coleta de lixo é muito alto
Problema: uma exceção de falta de memória é lançada
Há dois casos legítimos para um OutOfMemoryException gerenciado ser lançado:
Ficando sem memória virtual.
O coletor de lixo aloca memória do sistema em segmentos de um tamanho pré-determinado. Se uma alocação requer um segmento adicional mas não há nenhum bloco contíguo livre restante no espaço de memória virtual do processo, a alocação de heap gerenciado falha.
Não há memória física suficiente para alocar.
| Verificações de desempenho |
|---|
|
Determine se a exceção fora de memória é gerenciada. Determine a quantidade de memória virtual que pode ser reservada. Determine se há memória física suficiente. |
Se você determinar que a exceção não é legítima, entre em contato com o Serviço de Atendimento ao Cliente e Suporte da Microsoft com as seguintes informações:
- A pilha com a exceção de falta de memória gerenciada.
- Despejo de memória completo.
- Dados que provam que não é uma exceção de falta de memória legítima, incluindo dados que mostram que a memória virtual ou física não é um problema.
Problema: o processo usa muita memória
Uma suposição comum é que o uso da memória exibido na guia Desempenho do Gerenciador de Tarefas do Windows pode indicar quando muita memória está sendo usada. No entanto, essa exibição pertence ao conjunto de trabalho; ele não fornece informações sobre o uso de memória virtual.
Se você determinar que o problema é causado pelo heap gerenciado, meça o heap gerenciado ao longo do tempo para determinar eventuais padrões.
Se você determinar que o problema não é causado pelo heap gerenciado, use a depuração nativa.
Problema: o coletor de lixo não recupera objetos com rapidez suficiente
Quando parecer que os objetos não estão sendo recolhidos conforme o esperado para a coleta de lixo, você deve determinar se há referências fortes a esses objetos.
Você também poderá encontrar esse problema caso não tenha havido nenhuma coleta de lixo para a geração que contém um objeto inativo, o que indicará que o finalizador do objeto inativo não foi executado. Por exemplo, isso é possível quando você está executando um aplicativo STA (single-threaded apartment) e o thread que cuida da fila de finalizadores não é capaz de chamá-la.
| Verificações de desempenho |
|---|
|
Verifique referências a objetos. Determine se um finalizador foi executado. Determine se há objetos esperando para serem finalizados. |
Problema: o heap gerenciado está fragmentado demais
O nível de fragmentação é calculado como a taxa de espaço livre em relação à memória alocada total para a geração. Para a geração 2, um nível aceitável de fragmentação não é superior a 20%. Como a geração 2 pode ficar muito grande, a taxa de fragmentação é mais importante do que o valor absoluto.
Ter muito espaço livre na geração 0 não é um problema porque essa é a geração em que novos objetos são alocados.
No heap de objetos grandes sempre ocorre fragmentação, porque ele não é compactado. Objetos livres adjacentes são naturalmente recolhidos em um único espaço para atender a solicitações de alocação de objetos grandes.
A fragmentação pode se tornar um problema na geração 1 e na geração 2. Se essas gerações tiverem uma grande quantidade de espaço livre após uma coleta de lixo, o uso do objeto de um aplicativo poderá precisar de modificação e você deverá considerar reavaliar o tempo de vida de objetos de longo prazo.
A fixação excessiva de objetos pode aumentar a fragmentação. Se a fragmentação estiver alta, pode ter ocorrido a fixação de objetos demais.
Se a fragmentação da memória virtual estiver impedindo o coletor de lixo de adicionar segmentos, as causas poderão ser uma das seguintes:
Carregamento e descarregamento frequentes de muitos pequenos conjuntos.
Mantendo muitas referências a objetos COM ao interoperar com código não gerenciado.
Criação de objetos transitórios grandes, que faz com que o heap de objeto grande aloque e libere segmentos de heap frequentemente.
Ao hospedar o CLR, um aplicativo pode solicitar que o coletor de lixo mantenha seus segmentos. Isso reduz a frequência de alocações de segmento. Isso é feito usando o sinalizador STARTUP_HOARD_GC_VM na Enumeração STARTUP_FLAGS.
| Verificações de desempenho |
|---|
|
Calcule a quantidade de espaço livre no heap gerenciado. Determine o número de objetos fixados. |
Se você achar que não há nenhuma causa legítima para a fragmentação, entre em contato com o Serviço de Atendimento ao Cliente e Suporte da Microsoft.
Problema: pausas na coleta de lixo são muito longas
A coleta de lixo opera em tempo real flexível, portanto, um aplicativo deve ser capaz de tolerar algumas pausas. Um critério para o tempo real flexível é que 95% das operações deve terminar no prazo.
Na coleta de lixo simultânea, os threads gerenciados têm permissão para serem executados durante uma coleta, o que significa que as pausas são muito mínimas.
As coletas de lixo efêmeras (gerações 0 e 1) duram apenas alguns milissegundos, portanto, a diminuição das pausas geralmente não é viável. No entanto, você pode diminuir as pausas nas coleções de geração 2 alterando o padrão das solicitações de alocação de um aplicativo.
Outro método, mais preciso, é usar eventos ETW de coleta de lixo. Você pode encontrar os intervalos para coletas adicionando as diferenças de carimbo de data/hora a uma sequência de eventos. A sequência de coleta inteira inclui a suspensão do mecanismo de execução, a própria coleta de lixo e a retomada do mecanismo de execução.
Você pode usar notificações de coleta de lixo para determinar se um servidor está prestes a ter uma coleta de geração 2 e se o redirecionamento de solicitações para outro servidor pode aliviar problemas com pausas.
| Verificações de desempenho |
|---|
|
Determine o período de tempo em uma coleta de lixo. Determine o que causou uma coleta de lixo. |
Problema: a geração 0 é muito grande
É provável que a geração 0 tenha um número maior de objetos em um sistema de 64 bits, especialmente quando você usa a coleta de lixo do servidor em vez da coleta de lixo da estação de trabalho. Isso ocorre porque o limite para disparar uma coleta de lixo de geração 0 é maior nesses ambientes, e as coleções de geração 0 podem ficar muito maiores. O desempenho é melhorado quando um aplicativo aloca mais memória antes que uma coleta de lixo seja disparada.
Problema: O uso da CPU durante uma coleta de lixo é muito alto
O uso da CPU será alto durante uma coleta de lixo. Se um tempo de processo significativo for gasto em uma coleta de lixo, o número de coletas será muito frequente ou a coleta durará muito tempo. Uma maior taxa de alocação de objetos no heap gerenciado faz com que a coleta de lixo ocorra com mais frequência. Diminuir a taxa de alocação reduz a frequência das coletas de lixo.
Você pode monitorar as taxas de alocação usando o Allocated Bytes/second contador de desempenho. Para obter mais informações, consulte Contadores de Desempenho no .NET.
A duração de uma coleção é principalmente um fator do número de objetos que sobrevivem após a alocação. O coletor de lixo deve passar por uma grande quantidade de memória se restam muitos objetos a serem coletados. O trabalho para compactar os sobreviventes é demorado. Para determinar quantos objetos foram manipulados durante uma coleção, defina um ponto de interrupção no depurador no final de uma coleta de lixo para uma geração especificada.
| Verificações de desempenho |
|---|
|
Determine se o alto uso da CPU é causado pela coleta de lixo. Defina um ponto de interrupção no final da coleta de lixo. |
Diretrizes de solução de problemas
Esta seção descreve as diretrizes que você deve considerar ao iniciar suas investigações.
Coleta de lixo da estação de trabalho ou do servidor
Determine se você está usando o tipo correto de coleta de lixo. Se seu aplicativo usa vários threads e instâncias de objeto, use a coleta de lixo do servidor em vez da coleta de lixo da estação de trabalho. A coleta de lixo do servidor opera em vários threads, enquanto a coleta de lixo na estação de trabalho requer que várias instâncias de um aplicativo executem seus próprios threads de coleta de lixo e concorram por tempo de processamento da CPU.
Um aplicativo com baixa carga e que executa tarefas com pouca frequência em segundo plano, como um serviço, pode usar a coleta de lixo da estação de trabalho com coleta de lixo simultânea desabilitada.
Quando medir o tamanho do heap gerenciado
A menos que você esteja usando um criador de perfil, será necessário estabelecer um padrão de medição consistente para diagnosticar efetivamente problemas de desempenho. Considere os seguintes pontos para estabelecer um agendamento:
- Se você medir após uma coleta de lixo da geração 2, todo o heap gerenciado estará livre de lixo (objetos inativos).
- Se você medir imediatamente após uma coleta de lixo de geração 0, os objetos nas gerações 1 e 2 ainda não serão coletados.
- Se você medir imediatamente antes de uma coleta de lixo, você medirá o máximo de alocação possível antes do início da coleta de lixo.
- A medição durante uma coleta de lixo é problemática, pois as estruturas de dados do coletor de lixo não estão em um estado válido para passagem e podem não ser capazes de fornecer os resultados completos. Isso é por design.
- Quando você usa a coleta de lixo de estação de trabalho com coleta de lixo simultânea, os objetos recuperados não são compactados, portanto, o tamanho do heap pode ser igual ou maior (a fragmentação pode fazer com que pareça maior).
- A coleta de lixo simultânea na geração 2 é atrasada quando a carga de memória física é muito alta.
O procedimento a seguir descreve como definir um ponto de interrupção para que você possa medir o heap gerenciado.
Para definir um ponto de interrupção no final da coleta de lixo
No WinDbg com a extensão do depurador SOS carregada, insira o seguinte comando:
bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"Defina
GcCondemnedGenerationcomo a geração desejada. Esse comando requer símbolos privados.Esse comando força uma interrupção se
RestartEEfor executado após objetos de geração 2 terem sido recuperados para coleta de lixo.Na coleta de lixo do servidor, apenas um thread chama
RestartEE, portanto, o ponto de interrupção ocorrerá apenas uma vez durante uma coleta de lixo da geração 2.
Procedimentos de verificação de desempenho
Esta seção descreve os seguintes procedimentos para isolar a causa do problema de desempenho:
- Determine se o problema é causado pela coleta de lixo.
- Determine se a exceção fora de memória é gerenciada.
- Determine a quantidade de memória virtual que pode ser reservada.
- Determine se há memória física suficiente.
- Determine a quantidade de memória que o heap gerenciado está confirmando.
- Determine a quantidade de memória que o heap gerenciado reserva.
- Determine objetos grandes na geração 2.
- Determine referências a objetos.
- Determine se um finalizador foi executado.
- Determine se há objetos esperando para serem finalizados.
- Calcule a quantidade de espaço livre no heap gerenciado.
- Determine o número de objetos fixados.
- Determine o período de tempo em uma coleta de lixo.
- Determine o que disparou uma coleta de lixo.
- Determine se o alto uso da CPU é causado pela coleta de lixo.
Para determinar se o problema é causado pela coleta de lixo
Examine os dois contadores de desempenho de memória a seguir:
% de Tempo na GC. Exibe o percentual de tempo decorrido gasto na execução de uma coleta de lixo após o último ciclo de coleta de lixo. Use este contador para determinar se o coletor de lixo está gastando tempo demais para disponibilizar espaço de heap gerenciado. Se o tempo gasto na coleta de lixo for relativamente baixo, isso poderá indicar um problema de recurso fora do heap gerenciado. Esse contador pode não ser preciso quando a coleta de lixo simultânea ou em segundo plano está envolvida.
# Total de Bytes comprometidos. Exibe a quantidade de memória virtual comprometida atualmente pelo coletor de lixo. Use esse contador para determinar se a memória consumida pelo coletor de lixo é uma parte excessiva da memória que seu aplicativo usa.
A maioria dos contadores de desempenho de memória são atualizados no final de cada coleta de lixo. Portanto, elas podem não refletir as condições atuais sobre as quais você deseja obter informações.
Para determinar se a exceção de falta de memória é gerenciada
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o comando print exception (
pe):!peSe a exceção for gerenciada, OutOfMemoryException será exibida como o tipo de exceção, conforme mostrado no exemplo a seguir.
Exception object: 39594518 Exception type: System.OutOfMemoryException Message: <none> InnerException: <none> StackTrace (generated):Se a saída não especificar uma exceção, você precisará determinar de qual thread a exceção fora de memória é. Digite o comando a seguir no depurador para mostrar todos os threads com suas pilhas de chamadas:
~\*kbO thread com a pilha que tem chamadas de exceção é indicado pelo argumento
RaiseTheException. Esse é o objeto de exceção gerenciada.28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0Você pode usar o comando a seguir para despejar exceções aninhadas.
!pe -nestedSe você não encontrar exceções, a exceção de memória insuficiente se originou do código não gerenciado.
Para determinar a quantidade de memória virtual que pode ser reservada
No WinDbg com a extensão do depurador SOS carregada, insira o seguinte comando para obter a maior região livre:
!address -summaryA maior região livre é exibida conforme mostrado na saída a seguir.
Largest free region: Base 54000000 - Size 0003A980Neste exemplo, o tamanho da maior região livre é de aproximadamente 24.000 KB (3A980 em hexadecimal). Essa região é muito menor do que o que o coletor de lixo precisa para um segmento.
- ou -
Use o comando
vmstat:!vmstatA maior região livre é o maior valor encontrado na coluna MAXIMUM, conforme mostrado na saída a seguir.
TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~ Free: Small 8K 64K 46K 36 1,671K Medium 80K 864K 349K 3 1,047K Large 1,384K 1,278,848K 151,834K 12 1,822,015K Summary 8K 1,278,848K 35,779K 51 1,824,735K
Para determinar se há memória física suficiente
Inicie o Gerenciador de Tarefas do Windows.
Na guia
Performance, examine o valor confirmado. (No Windows 7, confiraCommit (KB)noSystem group.)Se o
Totalestiver próximo aoLimit, você está com pouca memória física.
Para determinar a quantidade de memória que o heap gerenciado está confirmando
Use o contador de desempenho de memória de
# Total committed bytespara obter o número de bytes que o heap gerenciado está confirmando. O coletor de lixo confirma partes em um segmento conforme necessário, nem todas ao mesmo tempo.Observação
Não use o
# Bytes in all Heapscontador de desempenho, pois ele não representa o uso real de memória pelo heap gerenciado. O tamanho de uma geração está incluído nesse valor e é, na verdade, o seu limite, ou seja, o tamanho que induz uma coleta de lixo se a geração for preenchida com objetos. Portanto, esse valor geralmente é zero.
Para determinar a quantidade de memória que o heap gerenciado reserva
Use o contador de desempenho de memória
# Total reserved bytes.O coletor de lixo reserva memória em segmentos e você pode determinar onde um segmento começa usando o
eeheapcomando.Importante
Embora você possa determinar a quantidade de memória que o coletor de lixo aloca para cada segmento, o tamanho do segmento é específico da implementação e está sujeito a alterações a qualquer momento, inclusive em atualizações periódicas. Seu aplicativo nunca deve fazer suposições sobre ou depender de um determinado tamanho de segmento, nem deve tentar configurar a quantidade de memória disponível para alocações de segmento.
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando:
!eeheap -gcO resultado é o seguinte.
Number of GC Heaps: 2 ------------------------------ Heap 0 (002db550) generation 0 starts at 0x02abe29c generation 1 starts at 0x02abdd08 generation 2 starts at 0x02ab0038 ephemeral segment allocation context: none segment begin allocated size 02ab0000 02ab0038 02aceff4 0x0001efbc(126908) Large object heap starts at 0x0aab0038 segment begin allocated size 0aab0000 0aab0038 0aab2278 0x00002240(8768) Heap Size 0x211fc(135676) ------------------------------ Heap 1 (002dc958) generation 0 starts at 0x06ab1bd8 generation 1 starts at 0x06ab1bcc generation 2 starts at 0x06ab0038 ephemeral segment allocation context: none segment begin allocated size 06ab0000 06ab0038 06ab3be4 0x00003bac(15276) Large object heap starts at 0x0cab0038 segment begin allocated size 0cab0000 0cab0038 0cab0048 0x00000010(16) Heap Size 0x3bbc(15292) ------------------------------ GC Heap Size 0x24db8(150968)Os endereços indicados por "segmento" são os endereços iniciais dos segmentos.
Para determinar objetos grandes na geração 2
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando:
!dumpheap –statSe o heap gerenciado for grande,
dumpheappode levar algum tempo para concluir.Você pode começar a analisar pelas últimas linhas do resultado, pois elas listam os objetos que usam mais espaço. Por exemplo:
2c6108d4 173712 14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo 00155f80 533 15216804 Free 7a747c78 791070 15821400 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700930 19626040 System.Collections.Specialized.ListDictionary 2c64e36c 78644 20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo 79124228 121143 29064120 System.Object[] 035f0ee4 81626 35588936 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 40182 90664128 System.Collections.Hashtable+bucket[] 790fa3e0 3154024 137881448 System.String Total 8454945 objectsO último objeto listado é uma cadeia de caracteres e ocupa mais espaço. Você pode examinar seu aplicativo para ver como os objetos de cadeia de caracteres podem ser otimizados. Para ver cadeias de caracteres entre 150 e 200 bytes, insira o seguinte:
!dumpheap -type System.String -min 150 -max 200Um exemplo dos resultados é o seguinte.
Address MT Size Gen 1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11 …Usar um inteiro em vez de uma cadeia de caracteres para uma ID pode ser mais eficiente. Se a mesma cadeia de caracteres está sendo repetida milhares de vezes, considere a centralização da cadeia de caracteres. Confira mais informações sobre a centralização da cadeia de caracteres no tópico de referência para o método String.Intern.
Para determinar referências a objetos
No WinDbg com a extensão do depurador SOS carregada, insira o seguinte comando para listar referências a objetos:
!gcroot- ou -
Para determinar as referências de um objeto específico, inclua o endereço:
!gcroot 1c37b2acRaízes encontradas em pilhas podem ser falsos positivos. Para obter mais informações, use o comando
!help gcroot.ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)-> 19010b78(DemoApp.FormDemoApp)-> 19011158(System.Windows.Forms.PropertyStore)-> … [omitted] 1c3745ec(System.Data.DataTable)-> 1c3747a8(System.Data.DataColumnCollection)-> 1c3747f8(System.Collections.Hashtable)-> 1c376590(System.Collections.Hashtable+bucket[])-> 1c376c98(System.Data.DataColumn)-> 1c37b270(System.Data.Common.DoubleStorage)-> 1c37b2ac(System.Double[]) Scan Thread 0 OSTHread 99c Scan Thread 6 OSTHread 484O
gcrootcomando pode levar muito tempo para ser concluído. Qualquer objeto que não seja recuperado pela coleta de lixo é um objeto ativo. Isso significa que alguma raiz está segurando direta ou indiretamente o objeto, portantogcroot, deve retornar informações de caminho para o objeto. Você deve examinar os grafos retornados e ver por que esses objetos ainda são referenciados.
Para determinar se um finalizador foi executado
Execute um programa de teste que contenha o seguinte código:
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();Se o teste resolver o problema, isso significa que o coletor de lixo não estava recuperando objetos, pois os finalizadores desses objetos haviam sido suspensos. O GC.WaitForPendingFinalizers método permite que os finalizadores concluam suas tarefas e corrija o problema.
Para determinar se há objetos esperando para serem finalizados
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando:
!finalizequeueExamine o número de objetos que estão prontos para finalização. Se o número for alto, você deve examinar por que esses finalizadores não conseguem progredir de forma alguma ou não conseguem progredir rápido o suficiente.
Para obter uma saída de threads, digite o seguinte comando:
!threads -specialEsse comando fornece uma saída como a seguinte.
OSID Special thread type 2 cd0 DbgHelper 3 c18 Finalizer 4 df0 GC SuspendEEO thread do finalizador indica qual finalizador, se houver, está sendo executado no momento. Quando um thread do finalizador não está executando nenhum finalizador, ele está aguardando que um evento o diga para fazer seu trabalho. Na maioria das vezes, você verá o thread do finalizador nesse estado porque ele é executado em THREAD_HIGHEST_PRIORITY e deve terminar de executar finalizadores, se houver, muito rapidamente.
Para determinar a quantidade de espaço livre no heap gerenciado
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando:
!dumpheap -type Free -statEsse comando exibe o tamanho total de todos os objetos gratuitos no heap gerenciado, conforme mostrado no exemplo a seguir.
total 230 objects Statistics: MT Count TotalSize Class Name 00152b18 230 40958584 Free Total 230 objectsPara determinar o espaço livre na geração 0, insira o seguinte comando para informações de consumo de memória por geração:
!eeheap -gcEsse comando exibe uma saída semelhante à seguinte. A última linha mostra o segmento efêmero.
Heap 0 (0015ad08) generation 0 starts at 0x49521f8c generation 1 starts at 0x494d7f64 generation 2 starts at 0x007f0038 ephemeral segment allocation context: none segment begin allocated size 00178250 7a80d84c 7a82f1cc 0x00021980(137600) 00161918 78c50e40 78c7056c 0x0001f72c(128812) 007f0000 007f0038 047eed28 0x03ffecf0(67103984) 3a120000 3a120038 3a3e84f8 0x002c84c0(2917568) 46120000 46120038 49e05d04 0x03ce5ccc(63855820)Calcule o espaço usado pela geração 0:
? 49e05d04-0x49521f8cO resultado é o seguinte. A geração 0 tem aproximadamente 9 MB.
Evaluate expression: 9321848 = 008e3d78O comando a seguir despeja o espaço livre dentro do intervalo de geração 0:
!dumpheap -type Free -stat 0x49521f8c 49e05d04O resultado é o seguinte.
------------------------------ Heap 0 total 409 objects ------------------------------ Heap 1 total 0 objects ------------------------------ Heap 2 total 0 objects ------------------------------ Heap 3 total 0 objects ------------------------------ total 409 objects Statistics: MT Count TotalSize Class Name 0015a498 409 7296540 Free Total 409 objectsA saída mostra que a parte de geração 0 do heap está usando 9 MB de espaço para objetos e tem 7 MB livres. Esta análise mostra até que ponto a geração 0 contribui para a fragmentação. Essa quantidade de uso do heap deve ser desconsiderada do valor total como a causa de fragmentação por objetos de longo prazo.
Para determinar o número de objetos presos
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando:
!gchandlesAs estatísticas exibidas incluem o número de identificadores fixados, tal como mostra o exemplo a seguir.
GC Handle Statistics: Strong Handles: 29 Pinned Handles: 10
Para determinar o período de tempo em uma coleta de lixo
Examine o contador de desempenho de memória de
% Time in GC.O valor é calculado usando um tempo de intervalo de exemplo. Como os contadores são atualizados no final de cada coleta de lixo, o exemplo atual terá o mesmo valor que o exemplo anterior se nenhuma coleção ocorreu durante o intervalo.
O tempo de coleta é obtido multiplicando o tempo de intervalo de exemplo com o valor percentual.
Os dados a seguir mostram quatro intervalos de amostragem de dois segundos, para um estudo de 8 segundos. As colunas
Gen0,Gen1eGen2mostram o número total de coletas de lixo que foram concluídas ao final do intervalo para essa geração.Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 10 2 10 3 1 1 3 11 3 1 3 4 11 3 1 3Essas informações não mostram quando a coleta de lixo ocorreu, mas você pode determinar o número de coletas de lixo que ocorreram em um intervalo de tempo. Supondo o pior caso, a coleta de lixo da décima geração 0 terminou no início do segundo intervalo e a coleta de lixo da 11ª geração 0 terminou no final do terceiro intervalo. O tempo entre o final da décima e o final da décima primeira coleta de lixo é cerca de dois segundos e o contador de desempenho mostra 3%, portanto, a duração da décima primeira coleta de lixo de geração 0 foi (dois segundos * 3% = 60 ms).
No próximo exemplo, há cinco intervalos.
Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 3 2 10 3 1 1 3 11 4 1 1 4 11 4 1 1 5 11 4 2 20A coleta de lixo da segunda geração 2 começou durante o quarto intervalo e terminou no quinto intervalo. Supondo o pior caso, a última coleta de lixo foi para uma coleta de geração 0 concluída no início do terceiro intervalo e a coleta de lixo de geração 2 concluída no final do quinto intervalo. Portanto, o tempo entre o final da coleta de lixo de geração 0 e o final da coleta de lixo de geração 2 é de 4 segundos. Como o
% Time in GCcontador é de 20%, a quantidade máxima de tempo que a coleta de lixo de geração 2 poderia ter levado é (4 segundos * 20% = 800ms).Como alternativa, você pode determinar o comprimento de uma coleta de lixo usando eventos ETW de coleta de lixo e analisar as informações para determinar a duração da coleta de lixo.
Por exemplo, os dados a seguir mostram uma sequência de eventos que ocorreu durante uma coleta de lixo não simultânea.
Timestamp Event name 513052 GCSuspendEEBegin_V1 513078 GCSuspendEEEnd 513090 GCStart_V1 517890 GCEnd_V1 517894 GCHeapStats 517897 GCRestartEEBegin 517918 GCRestartEEEndSuspender o thread gerenciado levou 26 us (
GCSuspendEEEnd–GCSuspendEEBegin_V1).A coleta de lixo real levou 4,8 ms (
GCEnd_V1–GCStart_V1).Retomar os threads gerenciados levou 21 us (
GCRestartEEEnd–GCRestartEEBegin).A saída a seguir fornece um exemplo para coleta de lixo em segundo plano e inclui os campos de processo, thread e evento. (Nem todos os dados são mostrados.)
timestamp(us) event name process thread event field 42504385 GCSuspendEEBegin_V1 Test.exe 4372 1 42504648 GCSuspendEEEnd Test.exe 4372 42504816 GCStart_V1 Test.exe 4372 102019 42504907 GCStart_V1 Test.exe 4372 102020 42514170 GCEnd_V1 Test.exe 4372 42514204 GCHeapStats Test.exe 4372 102020 42832052 GCRestartEEBegin Test.exe 4372 42832136 GCRestartEEEnd Test.exe 4372 63685394 GCSuspendEEBegin_V1 Test.exe 4744 6 63686347 GCSuspendEEEnd Test.exe 4744 63784294 GCRestartEEBegin Test.exe 4744 63784407 GCRestartEEEnd Test.exe 4744 89931423 GCEnd_V1 Test.exe 4372 102019 89931464 GCHeapStats Test.exe 4372O
GCStart_V1evento em 42504816 indica que se trata de uma coleta de lixo em segundo plano, porque o último campo é1. Essa se torna a coleta de lixo nº 102019.O
GCStartevento ocorre porque há a necessidade de uma coleta de lixo efêmera antes de iniciar uma coleta de lixo em segundo plano. Essa se torna-se a coleta de lixo nº 102020.Em 42514170, a coleta de lixo nº 102020 é concluída. Os threads gerenciados são reiniciados neste ponto. Isso é concluído no thread 4372, que disparou essa coleta de lixo em segundo plano.
No thread 4744, ocorre uma suspensão. Essa é a única vez em que a coleta de lixo em segundo plano precisa suspender os threads gerenciados. Essa duração é de aproximadamente 99ms ((63784407-63685394)/1000).
O evento
GCEndpara a coleta de lixo em segundo plano está em 89931423. Isso significa que a coleta de lixo em segundo plano durou cerca de 47 segundos ((89931423-42504816)/1000).Enquanto os threads gerenciados estão em execução, você pode ver qualquer número de coletas de lixo efêmero ocorrendo.
Para determinar o que disparou uma coleta de lixo
No depurador do WinDbg ou do Visual Studio com a extensão do depurador SOS carregada, insira o seguinte comando para mostrar todos os threads com suas pilhas de chamadas:
~*Kb
Esse comando exibe uma saída semelhante à seguinte.
0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4 0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48Se a coleta de lixo tiver sido causada por uma notificação de falta de memória do sistema operacional, a pilha de chamadas será semelhante, exceto pelo fato de que o thread é o thread do finalizador. O thread do finalizador obtém uma notificação assíncrona de memória insuficiente e induz à coleta de lixo.
Se a coleta de lixo tiver sido causada pela alocação de memória, a pilha será exibida da seguinte maneira:
0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration 0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1 0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18 0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b 0012f310 7a02ae4c mscorwks!Alloc+0x60 0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd 0012f424 300027f4 mscorwks!JIT_NewArr1+0x148 000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c 0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153Um auxiliar just-in-time (
JIT_New*) eventualmente chamaGCHeap::GarbageCollectGeneration. Se você determinar que as coletas de lixo na geração 2 são causadas por alocações, você deve identificar quais objetos são coletados durante uma coleta de lixo de geração 2 e como evitá-los. Ou seja, você deseja determinar a diferença entre o início e o final de uma coleta de lixo de geração 2 e os objetos que causaram a coleta de geração 2.Por exemplo, insira o seguinte comando no depurador para mostrar o início de uma coleção de geração 2:
!dumpheap –statSaída de exemplo (abreviada para mostrar os objetos que usam mais espaço):
79124228 31857 9862328 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 00155f80 21248 12256296 Free 79103b6c 297003 13068132 System.Threading.ReaderWriterLock 7a747ad4 708732 14174640 System.Collections.Specialized.HybridDictionary 7a747c78 786498 15729960 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 035f0ee4 89192 38887712 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 7912c444 91616 71887080 System.Double[] 791242ec 32451 82462728 System.Collections.Hashtable+bucket[] 790fa3e0 2459154 112128436 System.String Total 6471774 objectsRepita o comando no final da geração 2:
!dumpheap –statSaída de exemplo (abreviada para mostrar os objetos que usam mais espaço):
79124228 26648 9314256 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 79103b6c 296770 13057880 System.Threading.ReaderWriterLock 7a747ad4 708730 14174600 System.Collections.Specialized.HybridDictionary 7a747c78 786497 15729940 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 00155f80 13806 34007212 Free 035f0ee4 89187 38885532 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 32370 82359768 System.Collections.Hashtable+bucket[] 790fa3e0 2440020 111341808 System.String Total 6417525 objectsOs
double[]objetos desapareceram do final do resultado, o que significa que eles foram coletados. Esses objetos representam aproximadamente 70 MB. Os objetos restantes não mudaram muito. Portanto, essesdouble[]objetos foram o motivo pelo qual essa coleta de lixo de geração 2 ocorreu. Sua próxima etapa é determinar por que osdouble[]objetos estão lá e por que eles morreram. Você pode perguntar ao desenvolvedor de código de onde esses objetos vieram ou pode usar ogcrootcomando.
Para determinar se o alto uso da CPU é causado pela coleta de lixo
Correlacionar o valor do
% Time in GCcontador de desempenho de memória com o tempo de processo.Se o valor
% Time in GCaumentar ao mesmo tempo que o tempo de processo, a coleta de lixo estará causando um alto uso de CPU. Caso contrário, crie o perfil do aplicativo para localizar onde o alto uso está ocorrendo.