2010-01-19 20 views
5

He estado golpeando mi cabeza contra la pared tratando de descubrir cómo tuve una pérdida de memoria en una aplicación de basura recolectada de Cocoa. (El uso de la memoria en Activity Monitor simplemente crecerá y crecerá, y ejecutar la aplicación utilizando los instrumentos del GC Monitor también mostrará un gráfico en constante crecimiento.)Fuga de memoria con la recolección de basura de Cocoa

Finalmente lo reduje a un solo patrón en mi código. Los datos se cargaban en un NSData y luego se analizaban mediante una biblioteca C (los bytes y la longitud de los datos se pasaban a él). La biblioteca C tiene devoluciones de llamada que dispararían y devolverían punteros y longitudes de inicio de subcadena (para evitar la copia interna). Sin embargo, para mis propósitos, necesitaba convertirlos en NSStrings y mantenerlos por un tiempo. Hice esto usando el método initWithBytes: length: encoding: de NSString. Supuse que copiaría los bytes y NSString lo manejaría de manera adecuada, pero algo está yendo mal porque esto se pierde como loco.

Este código se "fuga" o de alguna manera engañar al recolector de basura:

- (void)meh 
{ 
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]]; 
    const int substrLength = 80; 

    for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) { 
     NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding]; 
     [cocoaString length]; 
    } 
} 

puedo poner esto en temporizador y simplemente ver el uso de memoria subir y subir con el Monitor de actividad, así como con el instrumento GC monitor . (holmes.txt es 594 KB)

Este no es el mejor código del mundo, pero muestra el problema. (Estoy ejecutando 10.6, el proyecto está dirigido a 10.5 - si eso importa). Leí los documentos de la recolección de basura y noté una serie de posibles dificultades, pero no creo que esté haciendo nada obviamente en contra de las reglas aquí. Sin embargo, no está de más preguntar. ¡Gracias!

Project zip

Así es una foto de la gráfico de objetos sólo crece y creciente:

alt text

+0

No puedo ver ninguna fuga cuando la ejecuto, oscila entre 7,5 MB y 9 MB de uso de memoria después de varios minutos de funcionamiento. –

+0

Eso es raro. Estoy en 10.6.2. Construí depuración y lanzamiento. Veo la memoria en constante crecimiento en todos los casos. wtf ... – Sean

+0

Ahora he tenido a otra persona en 10.5 ejecutarlo, y él informa que tampoco parece estar creciendo para él. Necesito a alguien más en 10.6+ para probarlo. – Sean

Respuesta

13

Este es un caso borde desafortunado. Por favor, presente un error (http://bugreport.apple.com/) y adjunte su excelente ejemplo mínimo.

El problema es doble;

  • El bucle de evento principal no se está ejecutando y, por lo tanto, el recopilador no se activa a través de la actividad MEL. Esto deja al recopilador haciendo su fondo normal solo colecciones basadas en umbrales.

  • Los datos almacenan los datos leídos del archivo en un búfer malloc'd que se asigna desde la zona malloc. Por lo tanto, la asignación de GC contabilizada, el objeto NSData en sí, es realmente pequeña, pero apunta a algo realmente grande (la asignación malloc). El resultado final es que el umbral del cobrador no se golpea y no se acumula. Obviamente, se desea mejorar este comportamiento, pero es un problema difícil.

Este es un error muy fácil de reproducir en una micro-referencia o en forma aislada. En la práctica, normalmente ocurre lo suficiente para que este problema no suceda. Sin embargo, puede haber ciertos casos en los que se vuelve problemático.

Cambie su código a esto y el recopilador recogerá los objetos de datos. Tenga en cuenta que no debe usar collectExhaustively con frecuencia; consume CPU.

- (void)meh 
{ 
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]]; 
    const int substrLength = 80; 

    for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) { 
     NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding]; 
     [cocoaString length]; 
    } 
    [data self]; 
    [[NSGarbageCollector defaultCollector] collectExhaustively]; 
} 

El [data self] mantiene el objeto de datos vivos después de la última referencia a la misma.

+0

Muchas gracias por la explicación y posible solución. Radar archivado: // 556417. – Sean

+1

Gracias - radar: // 7556417, por cierto. – bbum