2010-04-29 18 views
14

Recientemente investigué algunas "pérdidas de memoria" de .NET (es decir, objetos persistentes inesperados y arraigados de GC) en una aplicación de WinForms. Después de cargar y luego cerrar un gran informe, el uso de la memoria no cayó como se esperaba, incluso después de un par de colecciones gen2. Asumiendo que el control de informes estaba siendo mantenido vivo por un controlador de eventos parásitos abrí WinDbg para ver qué estaba pasando ....NET RegEx "fuga de memoria" investigación

Utilizando WinDbg, el comando !dumpheap -stat informó que una gran cantidad de memoria era consumida por instancias de cadena. Para refinar esto con el comando !dumpheap -type System.String encontré al culpable, una cadena de 90MB utilizada para el informe, en la dirección 03be7930. El último paso fue invocar !gcroot 03be7930 para ver qué objeto (s) lo mantenían con vida.

Mis expectativas no son correctos - no era un controlador de eventos desenganchado colgando en el control de informes (y la cadena de informe), pero en su lugar se lleva a cabo por una instancia System.Text.RegularExpressions.RegexInterpreter, que en sí es un descendiente de un System.Text.RegularExpressions.CachedCodeEntry. Ahora, el almacenamiento en memoria caché de Regexs es (algo) de conocimiento común ya que esto ayuda a reducir la sobrecarga de tener que volver a compilar Regex cada vez que se usa. ¿Pero qué tiene esto que ver con mantener viva mi cuerda?

Según el análisis con Reflector, resulta que la cadena de entrada se almacena en RegexInterpreter cuando se llama a un método Regex. RegexInterpreter se mantiene en esta referencia de cadena hasta que una nueva cadena es alimentada por una invocación subsiguiente del método Regex. Esperaría un comportamiento similar colgando en instancias Regex.Match y tal vez otros. La cadena es algo como esto:

  • Regex.Split, Regex.Match, Regex.Replace, etc
    • Regex.Run
      • RegexScanner.Scan (RegexScanner es la clase base, RegexInterpreter es la subclase descrita arriba).

El infractor expresiones regulares sólo se utiliza para la presentación de informes, rara vez se utiliza, y por lo tanto poco probable que se volverá a utilizar para limpiar la cadena de informe existente. E incluso si el Regex se usó en un momento posterior, probablemente estaría procesando otro informe grande. Este es un problema relativamente significativo y simplemente se siente sucio.

Dicho todo esto, encontré algunas opciones sobre cómo resolver, o al menos evitar, este escenario. Dejaré que la comunidad responda primero y, si no hay personas disponibles, llenaré los vacíos en uno o dos días.

+1

¿Está utilizando la opción 'Compiled' cuando crea el Regex? –

+0

No, la opción 'Compiled' no se usó en este caso. –

Respuesta

8

¿Está utilizando instancias de Regex o los métodos Regex estáticos que toman un patrón de cadena? According to this post, las instancias de Regex no participan en el almacenamiento en caché.

+2

Sí, el uso de métodos Regex estáticos fue el culpable.Puede verificar que el caché sea utilizado por los métodos estáticos a través de Reflector: todas las llamadas estáticas crean una Regex usando el ctor privado que toma el parámetro 'useCache'. La solución simple aquí es no utilizar los métodos estáticos. El almacenamiento en caché no es crítico porque la compilación es trivial en comparación con el procesamiento de las enormes cadenas de entrada. Otras soluciones que pueden ser útiles, dependiendo de cómo se use Regex, es deshabilitar el almacenamiento en caché Regex configurando Regex.CacheSize en 0 o ejecutando una cadena vacía a través de Regex después de procesar la fuente. –

0

Intente cambiar a un Regex compilado: la creación de instancias tarda más, pero tal vez no estará sujeto a esta extraña pérdida.

Ver http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regexoptions%28v=VS.100%29.aspx para más.

O no retenga la instancia de Regex más de lo necesario para crear una nueva para cada invocación de informe.

+2

Downvoted, porque [la expresión regular compilando SIEMPRE perderá memoria] (http://blog.codinghorror.com/to-compile-or-not-to-compile/), por diseño (de los ensamblajes no se pueden descargar a menos que se descargue el conjunto AppDomain): 'Sin embargo, hay aún más costos de compilación que deberían mencionarse. Emitir IL con Reflection.Emit carga mucho código y usa mucha memoria, y esa no es la memoria que nunca recuperarás. "A menos que lo hagas en un AppDomain desechable, que trae muchos nuevos desafíos. , como un rendimiento pobre entre AppDomain, por ejemplo, en comparación con el rendimiento en AppDomain. –