Original: http://www.mikel.cn/article.asp?id=1698
¿Conoce el mecanismo de recolección de basura de .Net? ¿Puedes describir brevemente cómo funciona GC? ¿Cómo podemos gestionar eficazmente la memoria? ¿Cuál es el papel del objeto instanciado en el cuerpo de la declaración Usando?
Esta sección está organizada de la siguiente manera: 1. Tipos de red y asignación de memoria 2. Cómo funciona el recolector de basura de GC 3. Qué son los recursos no administrados 4. Cómo liberar recursos de objetos de manera efectiva. Resumen Comencemos ahora nuestro estudio de esta sección.
1..Tipos de red y asignación de memoria
Todos los tipos en Net se derivan (directa o indirectamente) del tipo System.Object.
Los tipos en CTS se dividen en dos categorías: tipos de referencia (tipos de referencia, también llamados tipos administrados), que se asignan en el montón de memoria, y tipos de valor. Los tipos de valores se asignan en la pila. Como se muestra en la imagen
Los tipos de valor están en la pila, primero en entrar, último en salir. Las variables de tipo de valor tienen un orden de vida. Esto garantiza que las variables de tipo de valor liberarán recursos antes de que salgan del alcance. Más simple y más eficiente que los tipos de referencia. La pila asigna memoria de direcciones altas a direcciones bajas.
El tipo de referencia se asigna en el montón administrado (Montón administrado) y una variable se declara y guarda en la pila. Cuando se usa nuevo para crear un objeto, la dirección del objeto se almacena en esta variable. Por el contrario, el montón administrado asigna memoria desde direcciones bajas a direcciones altas, como se muestra en la figura.
2. Cómo funciona el recolector de basura GC
En la figura anterior, cuando el conjunto de datos caduca, no mostramos la destrucción del objeto y los objetos en el montón continúan existiendo, esperando el reciclaje de GC.
Se recomienda, aunque no es obligatorio, que el recolector de basura admita el envejecimiento de los objetos a través de la generación. Una generación es una unidad de objetos con edades relativas en la memoria. Objeto
El nombre en clave o edad identifica la generación a la que pertenece el objeto. En el ciclo de vida de la aplicación, los objetos creados más recientemente pertenecen a una generación más nueva y tienen valores más altos que los objetos creados anteriormente.
Número de subcódigo inferior. El código objeto de la generación más reciente es 0.
Al crear un nuevo objeto, primero debe buscar en la lista vinculada libre para encontrar el bloque de memoria más adecuado, asignarlo, ajustar la lista vinculada del bloque de memoria y fusionar fragmentos. La nueva operación se puede completar en casi O(1) tiempo, agregando 1 al puntero superior del montón. El principio de funcionamiento es: cuando el espacio restante en el montón administrado es insuficiente o el espacio del Generador 0 está lleno, el GC se ejecuta y comienza a recuperar memoria. Al comienzo de la recolección de basura, el GC comprime y ajusta la memoria del montón y los objetos se concentran en la parte superior. GC ocupará una cierta cantidad de tiempo de CPU al escanear basura. El algoritmo de GC original realmente escanea todo el montón, lo cual es ineficiente. El GC actual divide los objetos en el montón en tres generaciones. La entrada más reciente al montón es la generación 0, seguida de la generación 1 y la generación 2. El primer GC solo escanea la generación 0. Si el espacio recuperado es suficiente para el uso actual, no es necesario escanear objetos de otras generaciones. Por lo tanto, GC crea objetos de manera más eficiente que C++ y no necesita escanear todo el espacio del montón. La mejora del rendimiento aportada por la estrategia de escaneo y la estrategia de administración de memoria es suficiente para compensar el tiempo de CPU ocupado por el GC.
3. ¿Qué son los recursos no administrados?
Un recurso no administrado común es un objeto que encapsula un recurso del sistema operativo, como un archivo, una ventana o una conexión de red. Para dichos recursos, aunque el recolector de basura puede rastrear la vida útil del objeto que encapsula el recurso no administrado, sabe cómo hacerlo. limpiar estos recursos. Afortunadamente, el método Finalize() proporcionado por .net Framework permite que los recursos no administrados se limpien adecuadamente antes de que el recolector de basura los recicle. A continuación se muestran varios recursos no administrados comunes: pinceles, objetos de flujo, objetos componentes y otros recursos (Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, ApplicationContext, Brush,
Componente, ComponentDesigner, Contenedor, Contexto, Cursor, FileStream,
Fuente, Icono, Imagen, Matriz, Temporizador, Información sobre herramientas). (Consulte MSDN)
4. Cómo liberar eficazmente recursos no administrados.
GC no puede administrar recursos no administrados, entonces, ¿cómo liberar recursos no administrados? .Net proporciona dos métodos:
(1) Destructor: cuando el recolector de basura recicla los recursos de un objeto no administrado, llamará al método de finalización del objeto Finalize () para limpiar los recursos. Sin embargo, debido a las limitaciones de las reglas de trabajo del GC, el GC llama al Finalize del objeto. El recurso no se liberará una vez y el objeto se eliminará después de la segunda llamada.
(2) Heredar la interfaz IDisposable e implementar el método Dispose() La interfaz IDisposable define un patrón (con soporte a nivel de idioma), proporciona un determinado mecanismo para liberar recursos no administrados y evita los problemas inherentes con la recolección de basura de los destructores de dispositivos. -cuestiones relacionadas.
Para comprender mejor el mecanismo de recolección de basura, escribí especialmente parte del código y agregué comentarios detallados. Defina una única clase FrankClassWithDispose (hereda la interfaz IDisposable), FrankClassNoFinalize (sin finalizador), FrankClassWithDestructor (define el destructor).
El código específico es el siguiente:
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 2/18/2009
8 // Estudie la gestión de memoria .NET
9 // Recolector de basura recolector de basura. Los recursos alojados se pueden recuperar cuando sea necesario según las políticas,
10 // Pero el GC no sabe cómo gestionar los recursos no administrados. Como conexión de red, conexión de base de datos, pincel, componente, etc.
11 //Dos mecanismos para solucionar el problema de la liberación de recursos no gestionados. Destructor, interfaz IDispose
12 // recuento de referencias COM
13 // Gestión manual de C++, Nuevo Eliminar
14 // gestión automática de VB
15 espacios de nombres MemoryManagement
16 {
17 // Hereda la interfaz IDisposable, implementa el método Dispose y libera los recursos de instancia de FrankClassDispose
18 clase pública FrankClassWithDispose: IDisposable
19 {
20 Conexión Odbc privada _odbcConnection = nulo;
veintiuno
22 // Constructor
23 FrankClassWithDispose público ()
veinticuatro {
25 si (_odbcConnection == nulo)
26 _odbcConnection = nueva OdbcConnection();
27 Console.WriteLine( "Se ha creado FrankClassWithDispose ");
28 }
29 //Método de prueba
30 vacío público Hacer algo ()
31 {
32
33 /**/ /// /código aquí para hacer algo
34 regreso;
35}
36 // Implementar Disponer y liberar los recursos utilizados por esta clase.
37 eliminación pública vacía ()
38 {
39 si (_odbcConnection! = nulo)
40 _odbcConnection.Dispose();
41 Console.WriteLine( "FrankClassWithDispose ha sido eliminado");
42 }
43}
44 // Finalizar no está implementado, espere a que GC recicle los recursos de instancia de FrankClassFinalize y recíclelos directamente cuando GC se esté ejecutando.
45 clase pública FrankClassNoFinalize
46 {
47 Conexión Odbc privada _odbcConnection = nulo;
48 //Constructor
49 público FrankClassNoFinalize()
50 {
51 si (_odbcConnection == nulo)
52 _odbcConnection = nueva OdbcConnection();
53 Console.WriteLine( "Se ha creado FrankClassNoFinalize ");
54 }
55 //Método de prueba
56 vacío público Hacer algo()
57 {
58
59 // GC.Collect();
60 /**/ /// /código aquí para hacer algo
61 regresan;
62 }
63}
64 // Implementa el destructor, compílalo en el método Finalize y llama al destructor del objeto.
65 // Cuando GC se está ejecutando, el recurso no se libera en la primera llamada sino solo en la segunda llamada.
66 //Recursos de instancia de FrankClassDestructor
67 // El CLR utiliza un hilo independiente para ejecutar el método Finalize del objeto. Las llamadas frecuentes degradarán el rendimiento.
68 clase pública FrankClassWithDestructor
69 {
70 Conexión Odbc privada _odbcConnection = nulo;
71 //Constructor
72 público FrankClassWithDestructor()
73 {
74 si (_odbcConnection == nulo)
75 _odbcConnection = nueva OdbcConnection();
76 Console.WriteLine( "Se ha creado FrankClassWithDestructor ");
77 }
78 //Método de prueba
79 vacío público hacer algo()
80 {
81 /**/ /// /código aquí para hacer algo
82
83 regreso;
84}
85 // Destructor, libera recursos no administrados
86 ~ FrankClassWithDestructor()
87 {
88 si (_odbcConnection! = nulo)
89 _odbcConnection.Dispose();
90 Console.WriteLine( "FrankClassWithDestructor ha sido eliminado");
91 }
92 }
93}
94
Se utiliza una instancia del objeto no administrado OdbcConnection. El cliente creado fue probado brevemente. El código de cliente es el siguiente:
Código
1 usando Sistema;
2 usando System.Collections.Generic;
3 usando System.Text;
4 usando System.Data;
5 usando MemoryManagement;
6 // Codificado por Frank Xu Lei 2/18/2009
7 // Estudie la gestión de memoria .NET
8 // Pruebe los objetos no administrados recuperados.
9 // Pruebas de código no administrado, comparación
10 // Para el código administrado, GC puede reciclarlo por sí mismo de manera más estratégica, o puede implementar IDisposable, llamar al método Dispose() y liberarlo activamente.
11 espacio de nombres MemoryManagementClient
12 {
13 programas de clase
14 {
15 principal vacío estático (cadena [] argumentos)
16 {
17
18 /**/ ///////////////////////////////////////// //(1 ) / ///////////////////////////////////////////// //
19 // Llame al método Dispose () para liberarlo activamente. recursos, flexibilidad
20 FrankClassWithDispose _frankClassWithDispose = nulo;
21 intento
Veintidós {
23 _frankClassWithDispose = nuevo FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26}
27 finalmente
28 {
29 si (_frankClassWithDispose! = nulo)
30 _frankClassWithDispose.Dispose();
31 // Console.WriteLine("La instancia FrankClassWithDispose ha sido liberada");
32}
33
34 /**/ ///////////////////////////////////////// //(2 ) / ////////////////////////////////////////////////
35 // Puede utilizar la instrucción Usando para crear un objeto no administrado Antes de que finalice la ejecución del método, se llamará.
36 usando (FrankClassWithDispose _frankClassWithDispose2 = nuevo FrankClassWithDispose())
37 {
38 // _frankClassWithDispose2.DoSomething();
39 }
40
41 /**/ ///////////////////////////////////////// //(3 ) / ///////////////////////////////////////////// //
42 //Cuando el recolector de basura se está ejecutando, los recursos se liberan una vez
43 FrankClassNoFinalize _frankClassNoFinalize = nuevo FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 /**/ ////////////////////////////////////////////// (4) ///////////////////////////////////////////////// / /
47 // Cuando el recolector de basura se está ejecutando, se necesitan dos veces para liberar recursos.
48 FrankClassWithDestructor _frankClassWithDestructor = nuevo FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 /**/ ///////////////////////////////////////////// (5 ) ///////////////////////////////////////////////// /
51 // La declaración Usando no se puede utilizar para crear un objeto porque no implementa la interfaz IDispose.
52 // usando (FrankClassWithDestructor _frankClassWithDestructor2 = nuevo FrankClassWithDestructor())
53 // {
54 // _frankClassWithDestructor2.DoSomething();
55 // }
56
57 /**/ ////////////////////////////////////////////// /// /////////////////////////////////////////// //
58 // Para depurar
59 Console.WriteLine( "Presione cualquier tecla para continuar ");
60 Consola.ReadLine();
61
62
63}
64}
65 }
66
A veces los recursos deben liberarse en un momento específico. Una clase puede implementar la interfaz IDisposable que realiza la gestión de recursos y los métodos de tareas de limpieza IDisposable.Dispose.
Si la persona que llama necesita llamar al método Dispose para limpiar el objeto, la clase debe implementar el método Dispose como parte del contrato. El recolector de basura no llama por defecto.
Método Dispose; sin embargo, la implementación del método Dispose puede llamar a métodos en el GC para regular el comportamiento final del recolector de basura.
Vale la pena mencionar que: llamar al método Dispose () libera recursos de forma activa y es flexible. Puede usar la declaración Usando para crear objetos no administrados antes de que finalice la ejecución del método.
El método Dispose () libera recursos. El efecto de los dos extremos del código es el mismo. Puede ver el IL compilado.
Código
1. intentar
2 {
3 IL_0003: no
4 IL_0004: instancia de newobj nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
5 IL_0009: ubicación 0
6 IL_000a: ldloc 0.
7 IL_000b: instancia callvirt nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething()
8 IL_0010: no
9 IL_0011: no
10 IL_0012: salir.s IL_0028
11 } // fin .intentar
12 finalmente
13 {
14 IL_0014: no
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 0.
22 IL_0020: instancia callvirt nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose()
23 IL_0025: no
24 IL_0026: no
25 IL_0027: final por fin
26 } // controlador final
27 IL_0028: no
28 IL_0029: instancia de newobj nula [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
29 IL_002e: ubicación 1
30. intentar
31 {
32 IL_002f: no
33 IL_0030: no
34 IL_0031: salir.s IL_0045
35 } // fin .intenta
36 finalmente
37 {
38 IL_0033: ldloc 1.
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: ldloc 1.
45 IL_003e: instancia callvirt nula [mscorlib]System.IDisposable::Dispose()
46 IL_0043: no
47 IL_0044: final por fin
48 } // controlador final
49
La declaración Usando tiene el mismo efecto para liberar recursos de objetos no administrados. Esto se encuentra a menudo en entrevistas, como cuáles son los usos de la palabra clave Uso y preguntas similares. La respuesta ideal básica es que, además de hacer referencia al espacio de nombres y establecer alias para el espacio de nombres, este uso logra el reciclaje de recursos de objetos no administrados, como el bloque try finalmente. Sólo una forma sencilla de escribirlo.
Cuando utiliza el método Dispose para liberar un objeto no administrado, debe llamar a GC.SuppressFinalize. Si el objeto está en la cola de finalización, GC.SuppressFinalize evitará que el GC llame al método Finalize. Porque llamar al método Finalize sacrificará algo de rendimiento. Si su método Dispose ya ha limpiado los recursos delegados, no es necesario que el GC vuelva a llamar al método Finalize (MSDN) del objeto. Adjunto está el código MSDN para su referencia.
Código
clase pública BaseResource: IDisposable
{
//Apunta a recursos externos no administrados
identificador IntPtr privado;
// Otros recursos administrados utilizados por esta clase.
Componentes de componentes privados;
// Realiza un seguimiento de si se llama al método .Dispose, marca el bit y controla el comportamiento del recolector de basura
bool privado dispuesto = falso;
//Constructor
recurso base público()
{
// Inserte aquí el código de constructor apropiado.
}
// Implementar la interfaz IDisposable.
// No se puede declarar como método virtual virtual.
// Las subclases no pueden anular este método.
eliminación pública vacía ()
{
Eliminar (verdadero);
//Salir de la cola de finalización
//Establece el código finalizador de bloqueo del objeto
//
GC.SuppressFinalize (esto);
}
// Dispose(bool disposing) se ejecuta en dos situaciones diferentes.
// Si disponer es igual a verdadero, se ha llamado al método
// O llamado indirectamente mediante código de usuario. Se puede liberar tanto el código administrado como el no administrado.
// Si disponer es igual a false, el finalizador ha llamado internamente al método,
// No puedes hacer referencia a otros objetos, solo se pueden liberar recursos no administrados.
Eliminación de vacío virtual protegido (eliminación bool)
{
// Comprobar si se ha llamado a Dispose.
si (! este .dispuesto)
{
// Si es igual a verdadero, libera todos los recursos administrados y no administrados
si (desechando)
{
// Liberar recursos administrados.
Componentes.Dispose();
}
// Liberar recursos no administrados, si la eliminación es falsa,
// Sólo se ejecutará el siguiente código.
CloseHandle(manija);
manejar = IntPtr.Zero;
// Tenga en cuenta que esto no es seguro para subprocesos.
// Una vez liberado el recurso administrado, se pueden iniciar otros subprocesos para destruir el objeto.
// Pero antes de que el indicador eliminado se establezca en verdadero
// Si se requiere seguridad para subprocesos, el cliente debe implementarla.
}
dispuesto = verdadero;
}
//Utiliza interoperabilidad para llamar al método
// Borrar recursos no administrados.
[System.Runtime.InteropServices.DllImport( " Kernel32 " )]
CloseHandle booleano estático externo privado (identificador IntPtr);
//Utiliza el destructor de C# para implementar el código finalizador
// Esto solo se puede llamar y ejecutar si no se ha llamado al método Dispose.
// Si le das a la clase base la oportunidad de finalizar.
// No proporciones un destructor para las subclases.
~RecursoBase()
{
// No volver a crear el código de limpieza.
// Según consideraciones de confiabilidad y mantenibilidad, llamar a Dispose(false) es la mejor manera
Eliminar (falso);
}
// Le permite llamar al método Dispose varias veces,
// Pero se lanzará una excepción si el objeto ha sido liberado.
// No importa cuándo procese el objeto, comprobará si el objeto se libera.
// comprueba si se ha eliminado.
vacío público hacer algo()
{
si (este .dispuesto)
{
lanzar nueva ObjectDisposedException();
}
}
Para los tipos en los que llamar al método Close es más natural que al método Dispose, puede agregar un método Close a la clase base.
El método Close no toma parámetros y llama al método Dispose que realiza el trabajo de limpieza adecuado.
El siguiente ejemplo demuestra el método Close.
// No establezcas el método en virtual.
// Las clases heredadas no pueden anular este método
cierre público vacío()
{
// Llamar al parámetro Dispose sin parámetros.
Disponer();
}
vacío estático público principal ()
{
//Inserta código aquí para crear
// y utilizar un objeto BaseResource.
}