Original: http://www.mikel.cn/article.asp?id=1698
Você conhece o mecanismo de coleta de lixo do .Net? Você pode descrever brevemente como funciona o GC? Como podemos gerenciar a memória de maneira eficaz? Qual é a função do objeto instanciado no corpo da instrução Using?
Esta seção está organizada da seguinte forma: 1. Tipos de rede e alocação de memória 2. Como funciona o coletor de lixo GC 3. O que são recursos não gerenciados 4. Como liberar recursos de objeto de maneira eficaz. Resumo Vamos começar nosso estudo desta seção agora.
1..Tipos de rede e alocação de memória
Todos os tipos em Net são derivados (direta ou indiretamente) do tipo System.Object.
Os tipos no CTS são divididos em duas categorias - tipos de referência (tipos de referência, também chamados de tipos gerenciados), que são alocados no heap de memória, e tipos de valor. Os tipos de valor são alocados na pilha. Como mostrado na imagem
Os tipos de valor estão na pilha, primeiro a entrar, último a sair. As variáveis de tipo de valor têm uma ordem de vida. Isso garante que as variáveis de tipo de valor liberarão recursos antes de serem empurradas para fora do escopo. Mais simples e eficiente que os tipos de referência. A pilha aloca memória de endereços altos para endereços baixos.
O tipo de referência é alocado no heap gerenciado (Managed Heap) e uma variável é declarada e salva na pilha. Quando new é usado para criar um objeto, o endereço do objeto é armazenado nesta variável. Pelo contrário, o heap gerenciado aloca memória de endereços baixos para endereços altos, conforme mostrado na figura
2. Como funciona o coletor de lixo GC
Na figura acima, quando o dataSet expira, não exibimos a destruição do objeto, e os objetos no heap continuam a existir, aguardando a reciclagem do GC.
É recomendado, mas não obrigatório, que o coletor de lixo suporte o envelhecimento do objeto durante a geração. Uma geração é uma unidade de objetos com idades relativas na memória. Objeto
O codinome ou idade identifica a geração à qual o objeto pertence. No ciclo de vida da aplicação, os objetos criados mais recentemente pertencem a uma geração mais recente e possuem valores mais elevados do que os objetos criados anteriormente.
Número do subcódigo inferior. O código objeto na geração mais recente é 0.
Ao criar um novo objeto, você deve primeiro pesquisar a lista vinculada livre para encontrar o bloco de memória mais adequado, alocá-lo, ajustar a lista vinculada de blocos de memória e mesclar os fragmentos. A nova operação pode ser concluída quase em tempo O(1), adicionando 1 ao ponteiro superior do heap. O princípio de funcionamento é: Quando o espaço restante no heap gerenciado é insuficiente ou o espaço do Gerador 0 está cheio, o GC é executado e começa a recuperar memória. No início da coleta de lixo, o GC compacta e ajusta a memória heap e os objetos são concentrados no topo. O GC ocupará uma certa quantidade de tempo de CPU ao verificar o lixo. O algoritmo GC original realmente verifica o heap inteiro, o que é ineficiente. O GC atual divide os objetos no heap em três gerações. A entrada mais recente no heap é a geração 0, seguida pela geração 1 e pela geração 2. O primeiro GC verifica apenas a geração 0. Se o espaço recuperado for suficiente para o uso atual, não há necessidade de digitalizar objetos de outras gerações. Portanto, o GC cria objetos com mais eficiência do que C++ e não precisa verificar todo o espaço de heap. A melhoria de desempenho trazida pela estratégia de varredura e pela estratégia de gerenciamento de memória é suficiente para compensar o tempo de CPU ocupado pelo GC.
3. O que são recursos não gerenciados?
Um recurso não gerenciado comum é um objeto que agrupa um recurso do sistema operacional, como um arquivo, uma janela ou uma conexão de rede. Para esses recursos, embora o coletor de lixo possa rastrear o tempo de vida do objeto que agrupa o recurso não gerenciado, ele sabe como fazê-lo. limpar esses recursos. Felizmente, o método Finalize() fornecido pelo .net Framework permite que recursos não gerenciados sejam devidamente limpos antes que o coletor de lixo recicle tais recursos. Aqui estão vários recursos não gerenciados comuns: pincéis, objetos de fluxo, objetos de componentes e outros recursos (Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, ApplicationContext, Brush,
Componente,ComponentDesigner,Contêiner,Contexto,Cursor,FileStream,
Fonte, ícone, imagem, matriz, temporizador, dica de ferramenta). (Consulte MSDN)
4. Como liberar efetivamente recursos não gerenciados.
O GC não pode gerenciar recursos não gerenciados, então como liberar recursos não gerenciados? .Net fornece dois métodos:
(1) Destruidor: Quando o coletor de lixo recicla os recursos de um objeto não gerenciado, ele chamará o método de finalização do objeto Finalize() para limpar os recursos. No entanto, devido às limitações das regras de trabalho do GC, o GC chama o Finalize do objeto. O recurso não será liberado uma vez e o objeto será excluído após a segunda chamada.
(2) Herdar a interface IDisposable e implementar o método Dispose() A interface IDisposable define um padrão (com suporte em nível de linguagem), fornece um certo mecanismo para liberar recursos não gerenciados e evita os problemas inerentes à coleta de lixo de destruidores de dispositivos. -questões relacionadas.
Para entender melhor o mecanismo de coleta de lixo, escrevi especialmente parte do código e adicionei comentários detalhados. Defina uma única classe FrankClassWithDispose (herda a interface IDisposable), FrankClassNoFinalize (sem finalizador), FrankClassWithDestructor (define o destruidor).
O código específico é o seguinte:
Código
1 usando Sistema;
2 usando System.Collections.Generic;
3 usando System.Text;
4 usando System.Data;
5 usando System.Data.Odbc;
6 usando System.Drawing;
7 // Codificado por Frank Xu Lei 18/02/2009
8 // Estude o gerenciamento de memória .NET
9 // Coletor de lixo coletor de lixo. Os recursos hospedados podem ser recuperados quando necessário de acordo com as políticas,
10 // Mas o GC não sabe como gerenciar recursos não gerenciados. Como conexão de rede, conexão de banco de dados, pincel, componente, etc.
11 //Dois mecanismos para resolver o problema da libertação de recursos não geridos. Destruidor, interface IDispose
12 // Contagem de referência COM
13 // Gerenciamento manual C++, Nova Exclusão
14 // Gerenciamento automático VB
15 namespace MemoryManagement
16 {
17 // Herda a interface IDisposable, implementa o método Dispose e libera os recursos da instância de FrankClassDispose
18 classe pública FrankClassWithDispose: IDisposable
19 {
20 private OdbcConnection _odbcConnection = null;
vinte e um
22 // Construtor
23 público FrankClassWithDispose()
vinte e quatro {
25 se (_odbcConnection == nulo)
26 _odbcConnection = new OdbcConnection();
27 Console.WriteLine("FrankClassWithDispose foi criado");
28}
29 //Método de teste
30 público vazio DoSomething()
31 {
32
33 /**/ /// /codifique aqui para fazer algo
34 retorno;
35}
36 // Implemente Dispose e libere os recursos usados por esta classe
37 público vazio Dispose()
38 {
39 se (_odbcConnection! = nulo)
40 _odbcConnection.Dispose();
41 Console.WriteLine("FrankClassWithDispose foi descartado");
42}
43}
44 // Finalize não está implementado, espere que o GC recicle os recursos da instância de FrankClassFinalize e recicle-os diretamente quando o GC estiver em execução.
45 classe pública FrankClassNoFinalize
46 {
47 private OdbcConnection _odbcConnection = null;
48 //Construtor
49 público FrankClassNoFinalize()
50 {
51 se (_odbcConnection == nulo)
52 _odbcConnection = new OdbcConnection();
53 Console.WriteLine("FrankClassNoFinalize foi criado");
54}
55 //Método de teste
56 público vazio DoSomething()
57 {
58
59 //GC.Collect();
60 /**/ /// /codifique aqui para fazer algo
61 retorno;
62}
63}
64 // Implemente o destruidor, compile-o no método Finalize e chame o destruidor do objeto
65 // Quando o GC está rodando, o recurso não é liberado na primeira chamada, mas apenas na segunda chamada.
66 //Recursos de instância do FrankClassDestructor
67 // O CLR usa um thread independente para executar o método Finalize do objeto. Chamadas frequentes degradarão o desempenho.
68 classe pública FrankClassWithDestructor
69 {
70 private OdbcConnection _odbcConnection = null;
71 //Construtor
72 FrankClassWithDestructor público()
73 {
74 se (_odbcConnection == nulo)
75 _odbcConnection = new OdbcConnection();
76 Console.WriteLine("FrankClassWithDestructor foi criado");
77}
78 //Método de teste
79 público vazio DoSomething()
80 {
81 /**/ /// /codifique aqui para fazer algo
82
83 retorno;
84}
85 // Destruidor, libera recursos não gerenciados
86 ~FrankClassWithDestructor()
87 {
88 se (_odbcConnection! = nulo)
89 _odbcConnection.Dispose();
90 Console.WriteLine("FrankClassWithDestructor foi descartado");
91}
92}
93}
94
Uma instância do objeto não gerenciado OdbcConnection é usada. O cliente construído foi brevemente testado. O código do cliente é o seguinte:
Código
1 usando Sistema;
2 usando System.Collections.Generic;
3 usando System.Text;
4 usando System.Data;
5 usando Gerenciamento de Memória;
6 // Codificado por Frank Xu Lei 18/02/2009
7 // Estude o gerenciamento de memória .NET
8 // Teste os objetos não gerenciados recuperados.
9 // Testes para código não gerenciado, comparação
10 // Para código gerenciado, o GC pode reciclá-lo sozinho de forma mais estratégica ou pode implementar IDisposable, chamar o método Dispose() e liberá-lo ativamente.
11 namespace MemoryManagementClient
12 {
13 programas de aula
14 {
15 static void Main(string [] args)
16 {
17
18 /**/ ////////////////////////////////////// //(1 ) / /////////////////////////////////////// //
19 //Chame o método Dispose() para liberar ativamente. recursos, flexibilidade
20 FrankClassWithDispose _frankClassWithDispose = null;
21 tentativa
vinte e dois {
23 _frankClassWithDispose = new FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26}
27 finalmente
28 {
29 se (_frankClassWithDispose! = null)
30 _frankClassWithDispose.Dispose();
31 // Console.WriteLine("A instância de FrankClassWithDispose foi liberada");
32}
33
34 /**/ ////////////////////////////////////// //(2 ) / /////////////////////////////////////////// /
35 // Você pode usar a instrução Using para criar um objeto não gerenciado Antes que a execução do método termine, ele será chamado.
36 usando (FrankClassWithDispose _frankClassWithDispose2 = new FrankClassWithDispose())
37 {
38 // _frankClassWithDispose2.DoSomething();
39}
40
41 /**/ ////////////////////////////////////// //(3 ) / /////////////////////////////////////// //
42 //Quando o coletor de lixo está em execução, os recursos são liberados uma vez
43 FrankClassNoFinalize _frankClassNoFinalize = new FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 /**/ ////////////////////////////////////////// (4) //////////////////////////////////////////// //
47 // Quando o coletor de lixo está em execução, leva duas vezes para liberar recursos.
48 FrankClassWithDestructor _frankClassWithDestructor = new FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 /**/ ///////////////////////////////////////// (5) //////////////////////////////////////////// /
51 // A instrução Using não pode ser usada para criar um objeto porque não implementa a interface IDispose.
52 // usando (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor())
53 // {
54 // _frankClassWithDestructor2.DoSomething();
55 // }
56
57 /**/ ////////////////////////////////////////// /// ////////////////////////////////////// //
58 // Para depuração
59 Console.WriteLine("Pressione qualquer tecla para continuar");
60 Console.ReadLine();
61
62
63}
64}
65}
66
Às vezes, os recursos devem ser liberados em um momento específico. Uma classe pode implementar a interface IDisposable que executa métodos de tarefas de gerenciamento e limpeza de recursos IDisposable.Dispose.
Se o chamador precisar chamar o método Dispose para limpar o objeto, a classe deverá implementar o método Dispose como parte do contrato. O coletor de lixo não chama por padrão
Método Dispose; entretanto, a implementação do método Dispose pode chamar métodos no GC para regular o comportamento final do coletor de lixo.
Vale ressaltar que: chamar o método Dispose() libera recursos ativamente e é flexível. Você pode usar a instrução Using para criar objetos não gerenciados. Antes que a execução do método termine, ele será chamado.
O método Dispose() libera recursos. O efeito das duas extremidades do código é o mesmo.
Código
1. tente
2 {
3 IL_0003: não
4 IL_0004: instância newobj void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
5 IL_0009: stloc.
6 IL_000a: ldloc.
7 IL_000b: instância callvirt nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething()
8 IL_0010: não
9 IL_0011: não
10 IL_0012: licença IL_0028
11} // fim.tentar
12 finalmente
13 {
14 IL_0014: não
15 IL_0015: ldloc.0.
16 IL_0016: ldnulo
17 IL_0017: ceq
18 IL_0019: stloc.s CS$ 4$ 0000
19 IL_001b: ldloc.s CS$ 4$ 0000
20 IL_001d: brtrue.s IL_0026
21 IL_001f: ldloc.
22 IL_0020: instância callvirt nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose()
23 IL_0025: não
24 IL_0026: não
25 IL_0027: fimfinalmente
26 } // fim do manipulador
27 IL_0028: não
28 IL_0029: instância newobj void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
29 IL_002e: stloc.1
30. tente
31 {
32 IL_002f: não
33 IL_0030: não
34 IL_0031: licença IL_0045
35 } // fim.tentar
36 finalmente
37 {
38 IL_0033: ldloc1.
39 IL_0034: ldnulo
40 IL_0035: ceq
41 IL_0037: stloc.s CS$ 4$ 0000
42 IL_0039: ldloc.s CS$ 4$ 0000
43 IL_003b: brtrue.s IL_0044
44 IL_003d: ldloc1.
45 IL_003e: instância callvirt nula [mscorlib]System.IDisposable::Dispose()
46 IL_0043: não
47 IL_0044: fimfinalmente
48 } // fim do manipulador
49
A instrução Using tem o mesmo efeito para liberar recursos de objetos não gerenciados. Isso é frequentemente encontrado em entrevistas, como quais são os usos da palavra-chave Using e perguntas semelhantes. A resposta básica ideal é que, além de referenciar o namespace e definir aliases para o namespace, esse uso realiza a reciclagem de recursos de objetos não gerenciados, como o bloco try finalmente. Apenas uma maneira simples de escrever.
Ao usar o método Dispose para liberar um objeto não gerenciado, você deve chamar GC.SuppressFinalize. Se o objeto estiver na fila de finalização, GC.SuppressFinalize impedirá que o GC chame o método Finalize. Porque chamar o método Finalize sacrificará algum desempenho. Se o seu método Dispose já limpou os recursos delegados, não há necessidade de o GC chamar o método Finalize (MSDN) do objeto novamente. Em anexo está o código MSDN para sua referência.
Código
classe pública BaseResource: IDisposable
{
//Aponta para recursos externos não gerenciados
identificador IntPtr privado;
// Outros recursos gerenciados usados por esta classe.
Componentes de componentes privados;
// Rastreie se o método .Dispose é chamado, sinaliza o bit, controla o comportamento do coletor de lixo
bool privado disposto = falso;
//Construtor
baseResource público()
{
// Insira o código construtor apropriado aqui.
}
//Implementa a interface IDisposable.
// Não pode ser declarado como um método virtual virtual.
// As subclasses não podem substituir este método.
público vazio Dispose()
{
Dispor(verdadeiro);
//Sai da fila de finalização
//Definir o código finalizador de bloqueio do objeto
//
GC.SuppressFinalize(este);
}
// Dispose(bool disposing) é executado em duas situações diferentes.
// Se disposing for igual a true, o método foi chamado
// Ou chamado indiretamente pelo código do usuário. Tanto o código gerenciado quanto o não gerenciado podem ser liberados.
// Se disposing for igual a false, o método foi chamado internamente pelo finalizador,
// Você não pode fazer referência a outros objetos, apenas recursos não gerenciados podem ser liberados.
virtual vazio protegido Dispose( bool disposing)
{
// Verifica se Dispose foi chamado.
if ( ! this .disposed)
{
// Se for igual a verdadeiro, libera todos os recursos gerenciados e não gerenciados
se (descartando)
{
// Libera recursos gerenciados.
Componentes.Dispose();
}
// Libera recursos não gerenciados, se o descarte for falso,
// Somente o código a seguir será executado.
CloseHandle(alça);
identificador = IntPtr.Zero;
// Observe que isso não é seguro para threads.
// Após a liberação do recurso gerenciado, outros threads podem ser iniciados para destruir o objeto.
// Mas antes que o sinalizador descartado seja definido como verdadeiro
// Se a segurança do thread for necessária, o cliente deverá implementá-la.
}
disposto = verdadeiro;
}
//Use interoperabilidade para chamar o método
//Limpa recursos não gerenciados.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle (identificador IntPtr);
//Use o destruidor C# para implementar o código finalizador
// Isso só pode ser chamado e executado se o método Dispose não tiver sido chamado.
// Se você der à classe base uma chance de finalizar.
// Não forneça um destruidor para subclasses.
~BaseResource()
{
// Não recrie o código de limpeza.
// Com base em considerações de confiabilidade e facilidade de manutenção, chamar Dispose(false) é a melhor maneira
Dispor(falso);
}
//Permite chamar o método Dispose diversas vezes,
// Mas uma exceção será lançada se o objeto tiver sido liberado.
// Não importa quando você processa o objeto, você verificará se o objeto foi liberado.
// verifica se foi descartado.
public void DoSomething()
{
se (este .disposto)
{
lançar new ObjectDisposedException();
}
}
Para tipos em que chamar o método Close é mais natural que o método Dispose, você pode adicionar um método Close à classe base.
O método Close não aceita parâmetros e chama o método Dispose que executa o trabalho de limpeza apropriado.
O exemplo a seguir demonstra o método Close.
// Não defina o método como virtual.
// Classes herdadas não têm permissão para substituir este método
público vazio Fechar()
{
// Chama o parâmetro Dispose sem parâmetros.
Dispor();
}
público estático vazio Principal()
{
//Insira o código aqui para criar
// e use um objeto BaseResource.
}