2010-11-22 12 views
11

¿Por qué hace esto:.NET OutOfMemoryException

class OutOfMemoryTest02 
{ 
    static void Main() 
    { 
     string value = new string('a', int.MaxValue); 
    } 
} 

emitir la excepción; pero esto no:

class OutOfMemoryTest 
{ 
    private static void Main() 
    { 
     Int64 i = 0; 
     ArrayList l = new ArrayList(); 
     while (true) 
     { 
      l.Add(new String('c', 1024)); 

      i++; 
     } 
    } 
} 

¿Cuál es la diferencia?

+2

¿Qué hace el segundo? – SLaks

+0

segundo jst sigue funcionando hasta que mi máquina no responde y tengo que arrancarlo duro – ksm

+0

Puede valer la pena señalar para futuros visitantes de esta publicación que .net 4.5 elimina esta limitación si la estoy leyendo correctamente. http://msdn.microsoft.com/en-us/library/hh285054(v=vs.110).aspx – TravisWhidden

Respuesta

9

¿Has buscado int.MaxValue en los documentos? es el equivalente a 2GB, que probablemente sea más RAM de la que tiene disponible para un bloque contiguo de caracteres 'a'; eso es lo que está pidiendo aquí.

http://msdn.microsoft.com/en-us/library/system.int32.maxvalue.aspx

Su bucle infinito tiempo hará que la misma excepción (o uno diferente indirectamente relacionados con el uso excesivo de memoria RAM), pero va a tomar un tiempo. Intente aumentar 1024 a 10 * 1024 * 1024 para reproducir el síntoma más rápido en el caso del bucle.

Cuando ejecuto este tamaño de cadena más grande, obtengo la excepción en menos de 10 segundos después de 68 loops (verificando i).

+0

yup. entiendo ese hecho. Vengo del mundo JAVA, la máquina virtual se detendría si no hubiera más memoria del sistema disponible para asignar. pero en .net, particularmente el segundo ejemplo ... virtualmente puedo llevar el sistema a un estado sin respuesta y la máquina virtual nunca hace ruido ... ¿qué pasa con eso? – ksm

+0

Por lo tanto, en .Net obtendrá una 'OutOfMemoryException' en su lugar. –

+0

en JAVA no puedo agregar una nueva cadena a una lista sin fin, JVM da el error ... o es una cadena muy grande (como en el caso 1) o agrega muchas cadenas más pequeñas a una lista ... en ambos casos, JVM daría el error. – ksm

6

Su

new string('a', int.MaxValue); 

emite una OutOfMemoryException simplemente porque .NET de string tiene una limitación de longitud. La sección "Comentarios" en el MSDN docs dice:

El tamaño máximo de un objeto String en la memoria es de 2 GB, o aproximadamente mil millones de caracteres.

En mi sistema (.NET 4.5 x64) new string('a', int.MaxValue/2 - 31) tiros, mientras que new string('a', int.MaxValue/2 - 32) obras.

En su segundo ejemplo, el bucle infinito asigna ~ 2048 bloques de bytes hasta que su sistema operativo no pueda asignar ningún bloque más en el espacio de direcciones virtuales . Cuando se llegue a esto, obtendrás un OutOfMemoryException también.

(~ 2.048 bytes = 1024 caracteres * 2 bytes por UTF-16 + código de punto de cadena de bytes de cabecera)

probar este great article de Eric.

3

El objeto String puede usar un grupo de cadenas compartidas de respaldo para reducir el uso de memoria. En el primer caso, estás generando una cadena que es varios gigabytes. En el segundo caso, es probable que el compilador esté autointerning la cadena, por lo que está generando una cadena de 1024 bytes, y luego haciendo referencia a la misma cadena muchas veces.

Dicho esto, una ArrayList de ese tamaño debería acabar sin memoria, pero es probable que no haya dejado que el código se ejecute el tiempo suficiente para que se quede sin memoria.

+0

En realidad, el ctor de cadena no usará el grupo compartido. – SLaks

+0

-1 Esta es una cadena generada en tiempo de ejecución que no será internada. –

+0

lo dejé funcionar ... de hecho, inicialmente había ejecutado el programa sin demoras entre asignaciones posteriores y mi computadora dejó de responder en menos de 10 ... – ksm

4

Porque int.MaxValue es 2.147.483.647 o 2 gigabytes, que deben asignarse contiguamente.

En el segundo ejemplo, el sistema operativo solo necesita encontrar 1024 bytes para asignar cada vez y puede intercambiar al disco duro.Estoy seguro que si usted lo dejó correr el tiempo suficiente que terminarías en un lugar oscuro :)

+1

terminé en un lugar (muy) oscuro :) ¿El VM nunca me dirá que me voy a quedar sin pilas? Solo puedo agregar numerosas pequeñas variables a la memoria ... para siempre. – ksm

0

En su primera muestra que está tratando de crear una cadena de 2 g en un tiempo

En el segundo ejemplo se mantiene agregando 1k a una matriz. Tendrá que repetir el ciclo más de 2 millones de veces para alcanzar la misma cantidad de consumo.

Y tampoco está todo almacenado a la vez, en una variable. Por lo tanto, parte del uso de la memoria puede persistir en el disco para dar cabida a los nuevos datos, creo.

0

Debido a que un solo objeto cannot have more than 2 GB:

En primer lugar algunos antecedentes; en la versión 2.0 del .NET Runtime (CLR) hicimos una decisión de diseño consciente para mantener el tamaño máximo permitido en el objeto GC Montón a 2 GB, incluso en la versión de 64 bits del tiempo de ejecución

En su primer ejemplo, intentas asignar un objeto de 2 GB, con la sobrecarga del objeto (8 bytes?) es simplemente demasiado grande.

No sé cómo funciona ArrayList internamente, pero se asignan varios objetos de 2 GB cada uno y ArrayList, por lo que sé, solo contiene punteros que son 4 (8 en x64?) Bytes, independientemente del tamaño del objeto al que apuntan es.

Para citar another article:

Asimismo, los objetos que tienen referencias a otro almacén de objetos sólo la referencia. Por lo tanto, si tiene un objeto que contiene referencias a otros tres objetos, la huella de memoria solo tiene 12 bytes adicionales: un puntero de 32 bits para cada uno de los objetos a los que se hace referencia. No importa cuán grande sea el objeto al que se hace referencia.

+0

ArrayList no puede contener 2 GB de punteros. – SLaks

2

El segundo fragmento se bloqueará también. Simplemente lleva más tiempo sin funcionar porque consume memoria mucho más lento. Presta atención a la luz de acceso al disco duro, parpadea furiosamente mientras Windows saca las páginas de la RAM para hacer espacio. El primer constructor de cadena falla inmediatamente ya que el administrador de pila no le permitirá asignar 4 gigabytes.

+0

O, 2 gigabytes;) –

+0

¡Finalmente, una respuesta correcta! – SLaks

+1

@Moo: las Caracteres tienen dos bytes de ancho. – SLaks

1

Ambas versiones provocarán una excepción OOM, es solo que (en una máquina de 32 bits) la obtendrá inmediatamente con la primera versión cuando intente asignar un objeto "grande" muy grande.

La segunda versión se llevará mucho más tiempo, sin embargo, ya que habrá una gran cantidad de golear para llegar a la condición OOM por un par de factores:

  • Se le asignando millones de pequeños objetos que son todos alcanzable por el GC. Una vez que empiece a poner el sistema bajo presión, el GC gastará una cantidad desmesurada de tiempo explorando generaciones con millones y millones de objetos. Esto tomará una cantidad de tiempo considerable y comenzará a causar estragos con la paginación ya que la memoria fría y caliente constantemente entra y sale a medida que se escanean las generaciones.

  • Habrá una gran cantidad de páginas ya que GC escanea millones de objetos en varias generaciones para intentar liberar memoria. El escaneo ocasionará que se filtren grandes cantidades de memoria constantemente.

El thrashing hará que el sistema para moler a una sobrecarga de procesamiento alto y por lo tanto la condición OOM tomará un largo tiempo para ser alcanzado. La mayor parte del tiempo se gastará en el GC y en la búsqueda de la segunda versión.

0

Una de las razones por las que su sistema podría detenerse es porque el código de .NET se ejecuta más cerca del metal y está en un circuito cerrado que debe consumir 100% de CPU siempre que la prioridad del proceso lo permita. Si desea evitar que la aplicación consuma demasiada CPU mientras realiza el ciclo cerrado, debe agregar algo como System.Threading.Thread.Sleep(10) al final del ciclo, lo que forzará el tiempo de procesamiento de otros hilos.

Una diferencia importante entre el CLR de JVM y .NET (Common Language Runtime) es que CLR no limita el tamaño de su memoria en un sistema/aplicación x64 (en aplicaciones de 32 bits, sin el indicador de dirección grande el sistema operativo limita cualquier aplicación a 2GB debido a limitaciones de direccionamiento). El compilador JIT crea código nativo de Windows para su arquitectura de procesamiento y luego lo ejecuta en el mismo ámbito que cualquier otra aplicación de Windows. La JVM es una zona de pruebas más aislada que limita la aplicación a un tamaño específico dependiendo de los parámetros de configuración/línea de comandos.

En cuanto a las diferencias entre los dos algoritmos:

La creación cuerda suelta no está garantizada a fallar cuando se ejecuta en un entorno de 64 bits con suficiente memoria contigua para asignar el 4 GB necesario contener caracteres int.MaxValue (cadenas .NET son Unicode por defecto, lo que requiere 2 bytes por carácter). Una aplicación de 32 bits siempre fallará, incluso con la bandera Large Address Aware configurada porque la memoria máxima es todavía algo así como 3.5GB.

La versión de ciclo While de su código probablemente consumirá más memoria total, siempre que tenga suficiente disponible, antes de lanzar la excepción porque las cadenas se pueden asignar en fragmentos más pequeños, pero se garantiza que llegue al error (aunque tiene muchos recursos, podría suceder como resultado de que ArrayList excediera la cantidad máxima de elementos en una matriz en lugar de la incapacidad de asignar espacio nuevo para una cadena pequeña). Kent Murra también tiene razón sobre el interrogatorio de cuerdas; necesitará aleatorizar la longitud de la cadena o el contenido de los caracteres para evitar la internación, de lo contrario, simplemente está creando punteros a la misma cadena. La recomendación de Steve Townsend de aumentar la longitud de la cuerda también dificultaría la búsqueda de bloques contiguos de memoria lo suficientemente grandes, lo que permitirá que la excepción ocurra más rápidamente.

EDIT:

pensé que le daría algunos enlaces personas les puede resultar útil para la comprensión de la memoria .NET:

Estos dos artículos son un poco más viejo, pero muy bueno en la profundidad de lectura:

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework

Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Framework

Estos son los blogs de una colección de basura desarrollar .NET er para obtener información sobre la versión más reciente de gestión de memoria .NET:

So, what’s new in the CLR 4.0 GC?

CLR 4.5: Maoni Stephens - Server Background GC

Este SO pregunta puede ayudarle a observar el funcionamiento interno de.NET memoria:

.NET Memory Profiling Tools

Cuestiones relacionadas